今天来分享一下时间轴,最近看很多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