今天来分享一下时间轴,最近看很多App里面都很常见,水平一般,所以仿了一个。 先上张图片:
代码: MainActivity:
public class MainActivity extends AppCompatActivity { private MyView myView; private View activity_main; private View top; private View bottom; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //通过LayoutInflater动态加载布局,主要取到activity_main,这个布局,然后通过setContentView进行设置,为什么不直接设置setContentView(R.layout.activity_main); //因为在onResume之前都无法得到View测量的结果,width和heigh都是0,所以通过addOnGlobalLayoutListener进行回掉(当onMeasure,onLayot执行完毕会回掉,才能拿到宽高) LayoutInflater layoutInflater = LayoutInflater.from(this); activity_main = layoutInflater.inflate(R.layout.activity_main, null); //得到自定义View myView = (MyView) activity_main.findViewById(R.id.myView); //分别得到两段文字的TextView top = activity_main.findViewById(R.id.top); bottom = activity_main.findViewById(R.id.bottom); setContentView(activity_main); } @Override protected void onStart() { super.onStart(); //activity_main布局全部构建完毕会回调 ViewTreeObserver Observer = activity_main.getViewTreeObserver(); Observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { //移除监听 activity_main.getViewTreeObserver().removeOnGlobalLayoutListener(this); //设置文本 myView.setText(new String[]{"下","中","上"}); //分别设置两段文字的高 myView.setViewHeight(new int[]{bottom.getTop(),top.getTop()+200,top.getTop()}); //强制View重绘 myView.mInvalidate(); myView.setRadius(36); } }); } }自定义View:
public class MyView extends View { //绘制圆的半径 private int radius; //每段文字的高(决定着小球的高) private int[] viewHeight; //小圆球上面的文本 private String[] text; private Paint p; private Rect mBounds; public MyView(Context context) { super(context); init(); } public MyView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { //初始化画笔 p = new Paint(); p.setColor(Color.RED); p.setAntiAlias(true); mBounds = new Rect(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int heightsize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(radius*2,heightsize); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } public void setViewHeight(int[] viewHeight) { this.viewHeight = viewHeight; } public void setText(String[] text) { this.text = text; } public void setRadius(int radius) { this.radius = radius; } @Override protected void onDraw(Canvas canvas) { for (int i = 0; i < text.length; i++) { p.setColor(Color.RED); int height = viewHeight[i]; //拿到第一个的高,和下一个的高,用作画线,先让下一个的高等于前一个的高,如果下一个要是在数组没有,会报错,所以要捕获. int nextheight = height; try { nextheight = viewHeight[i + 1]; } catch (Exception e) { } //拿到当前文字 String str = text[i]; //画小球 canvas.drawCircle(radius, height + radius, radius, p); p.setColor(Color.WHITE); p.setTextSize(30); //这个是得到文字的宽和高 p.getTextBounds(str, 0, str.length(), mBounds); float str1W = mBounds.width(); float str1H = mBounds.height(); //画文字 canvas.drawText(str, radius - str1W / 2, height + radius + str1H / 2, p); p.setColor(Color.RED); //画线 canvas.drawLine(radius, height, radius, nextheight, p); } } public void mInvalidate() { //重新绘制onMeasure->onLayout->onDraw requestLayout(); } }布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="300dp" android:orientation="horizontal" tools:context="com.example.yaoyan.myapplication.MainActivity"> <LinearLayout android:layout_width="0dp" android:gravity="center_horizontal" android:orientation="vertical" android:layout_height="match_parent" android:layout_weight="1"> <com.example.yaoyan.myapplication.MyView android:id="@+id/myView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="3" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:id="@+id/top" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:text="邀请微信好友" android:textSize="30sp" /> <TextView android:id="@+id/bottom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="150dp" android:text="让好友发送985760到你的微信" /> </LinearLayout> </LinearLayout>这里说一下遇到的坑吧!
<com.example.yaoyan.myapplication.MyView android:id="@+id/myView" android:layout_width="wrap_content" android:layout_height="wrap_content" />我在这里宽和高都设置成为包裹内容,但却发现外层父布局的
android:gravity="center_horizontal"失效,后来发现没有重写
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int heightsize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(radius*2,heightsize); }这个方法 这个方法是测量宽高的,我们来看看调用的super都干了些什么!?
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }setMeasuredDimension 这个方法就设置测量之后的宽高了,那我们来看看getDefaultSize 这个方法干了什么:
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }这里发现一个问题,不管是MeasureSpec.AT_MOST 还是MeasureSpec.EXACTLY 他最后的返回结果都是specSize,specSize是从MeasureSpec.getSize(measureSpec); 得到的。measureSpec 是传递过来的widthMeasureSpec (只拿宽举例子)那么这个是从哪里来的呢!? 最后发现是从ViewGroup里面传递来的:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);调用了getChildMeasureSpec方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }这里发现
case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; ... return MeasureSpec.makeMeasureSpec(resultSize, resultMode);这里发现,如果父类是一个精确模式MeasureSpec.EXACTLY,如果子类是一个数值那么:
resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;如果子类是一个LayoutParams.MATCH_PARENT那么:
resultSize = size; resultMode = MeasureSpec.EXACTLY;如果子类是一个LayoutParams.WRAP_CONTENT那么:
resultSize = size; resultMode = MeasureSpec.AT_MOST;最后从上面的代码,再结合下面讲的:
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }就可以知道不管是MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY,最后的结果都是父类的宽或者是高,所以我们在xml文件里写 android:gravity=”center_horizontal”没有作用 所以,我们要在onMeasure中重写:宽
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int heightsize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(radius*2,heightsize); }到这里问题就解决了! 如果你喜欢分享,一起加入群:524727903
