今天來分享一下時間軸,最近看很多App裡面都很常見,水準一般,是以仿了一個。
先上張圖檔:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90ERPVTRU5UNrpWTmZEWjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TM5YTMwkzM5ADMxMDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
代碼:
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()+,top.getTop()});
//強制View重繪
myView.mInvalidate();
myView.setRadius();
}
});
}
}
自定義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*,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 = ; i < text.length; i++) {
p.setColor(Color.RED);
int height = viewHeight[i];
//拿到第一個的高,和下一個的高,用作畫線,先讓下一個的高等于前一個的高,如果下一個要是在數組沒有,會報錯,是以要捕獲.
int nextheight = height;
try {
nextheight = viewHeight[i + ];
} catch (Exception e) {
}
//拿到目前文字
String str = text[i];
//畫小球
canvas.drawCircle(radius, height + radius, radius, p);
p.setColor(Color.WHITE);
p.setTextSize();
//這個是得到文字的寬和高
p.getTextBounds(str, , str.length(), mBounds);
float str1W = mBounds.width();
float str1H = mBounds.height();
//畫文字
canvas.drawText(str, radius - str1W / , height + radius + str1H / , 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" />
我在這裡寬和高都設定成為包裹内容,但卻發現外層父布局的
失效,後來發現沒有重寫
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightsize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(radius*,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(, specSize - padding);
int resultSize = ;
int resultMode = ;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= ) {
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 >= ) {
// 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 >= ) {
// 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 ? : 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 ? : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
這裡發現
case MeasureSpec.EXACTLY:
if (childDimension >= ) {
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*,heightsize);
}
到這裡問題就解決了!
如果你喜歡分享,一起加入群:524727903