最近修改项目遇到查看物流这个需求,经过一个下午的时间的终于搞定,趁着这个时间点,赶快把这个功能抽取出来,方便大家以后开发的需要,帮助到更多的人 先看效果图,如下 看完之后,分析可知道,主要是两部分,一个头部和一个body. 那我们最主要的工作就是body内容的实现,头部的实现简单,这里就不再详细的说明 这里我给大家提供一个github上的开源项目,不过这个实现起来,绘制的效果比较慢,不过也可以实现相同的效果 github作者的博客地址
首先我们自定义个类继承自LinearLayout控件, 继承几个构造方法
public class UnderLineLinearLayout extends LinearLayout{ public UnderLineLinearLayout(Context context) {this(context, null);} public UnderLineLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public UnderLineLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context,attrs,defStyleAttr);}我们看时间轴,可以分析得到,原点分为绿色和灰色两种状态,字体也是如此 初始化一些参数和使用工具
//=============================================================line gravity常量定义 public static final int GRAVITY_LEFT = 2; public static final int GRAVITY_RIGHT = 4; public static final int GRAVITY_MIDDLE =0; public static final int GRAVITY_TOP = 1; public static final int GRAVITY_BOTTOM = 3; //=============================================================元素定义 private Bitmap mIcon; //line location private int lineMarginSide; private int lineDynamicDimen; //line property private int lineStrokeWidth; private int lineColor; //point property private int pointSize; private int pointColor; //=============================================================paint private Paint linePaint; private Paint pointPaint; //=============================================================其他辅助参数 //第一个点的位置 private int firstX; private int firstY; //最后一个图的位置 private int lastX; private int lastY; //默认垂直 private int curOrientation = VERTICAL; //line gravity(默认垂直的左边) private int lineGravity = GRAVITY_LEFT; private Context mContext; //开关 private boolean drawLine = true; private int rootLeft; private int rootMiddle; private int rootRight; private int rootTop; private int rootBottom; //参照点 private int sideRelative;整个的源码如下
public class UnderLineLinearLayout extends LinearLayout { //=============================================================line gravity常量定义 public static final int GRAVITY_LEFT = 2; public static final int GRAVITY_RIGHT = 4; public static final int GRAVITY_MIDDLE =0; public static final int GRAVITY_TOP = 1; public static final int GRAVITY_BOTTOM = 3; //=============================================================元素定义 private Bitmap mIcon; //line location private int lineMarginSide; private int lineDynamicDimen; //line property private int lineStrokeWidth; private int lineColor; //point property private int pointSize; private int pointColor; //=============================================================paint private Paint linePaint; private Paint pointPaint; //=============================================================其他辅助参数 //第一个点的位置 private int firstX; private int firstY; //最后一个图的位置 private int lastX; private int lastY; //默认垂直 private int curOrientation = VERTICAL; //line gravity(默认垂直的左边) private int lineGravity = GRAVITY_LEFT; private Context mContext; //开关 private boolean drawLine = true; private int rootLeft; private int rootMiddle; private int rootRight; private int rootTop; private int rootBottom; //参照点 private int sideRelative; public UnderLineLinearLayout(Context context) { this(context, null); } public UnderLineLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public UnderLineLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.UnderLineLinearLayout); lineMarginSide = attr.getDimensionPixelOffset(R.styleable.UnderLineLinearLayout_line_margin_side, 10); lineDynamicDimen = attr.getDimensionPixelOffset(R.styleable.UnderLineLinearLayout_line_dynamic_dimen, 0); lineStrokeWidth = attr.getDimensionPixelOffset(R.styleable.UnderLineLinearLayout_line_stroke_width, 2); lineColor = attr.getColor(R.styleable.UnderLineLinearLayout_line_v_color, 0xff3dd1a5); pointSize = attr.getDimensionPixelSize(R.styleable.UnderLineLinearLayout_point_size, 8); pointColor = attr.getColor(R.styleable.UnderLineLinearLayout_point_color, 0xff3dd1a5); lineGravity = attr.getInt(R.styleable.UnderLineLinearLayout_line_gravity, GRAVITY_LEFT); int iconRes = attr.getResourceId(R.styleable.UnderLineLinearLayout_icon_src, R.drawable.point); // mIcon = BitmapFactory.decodeResource(context.getResources(), iconRes); Drawable temp = context.getResources().getDrawable(iconRes); if (drawableToBitmap(temp)!=null) mIcon=drawableToBitmap(temp); // BitmapDrawable bd = (BitmapDrawable) temp; // Bitmap bm = bd.getBitmap(); // if (bm != null) mIcon = bm; curOrientation = getOrientation(); attr.recycle(); setWillNotDraw(false); initView(context); } public static Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = Bitmap.createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } private void initView(Context context) { this.mContext = context; linePaint = new Paint(); linePaint.setAntiAlias(true); linePaint.setDither(true); linePaint.setColor(lineColor); linePaint.setStrokeWidth(lineStrokeWidth); linePaint.setStyle(Paint.Style.FILL_AND_STROKE); pointPaint = new Paint(); pointPaint.setAntiAlias(true); pointPaint.setDither(true); pointPaint.setColor(pointColor); pointPaint.setStyle(Paint.Style.FILL); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); calculateSideRelative(); if (drawLine) { drawTimeLine(canvas); } } private void calculateSideRelative() { rootLeft = getLeft(); rootTop = getTop(); rootRight = getRight(); rootBottom = getBottom(); if (curOrientation == VERTICAL) rootMiddle = (rootLeft + rootRight) >> 1; if (curOrientation == HORIZONTAL) rootMiddle = (rootTop + rootBottom) >> 1; boolean isCorrect=(lineGravity==GRAVITY_MIDDLE||(lineGravity+curOrientation)%2!=0); if (isCorrect){ switch (lineGravity){ case GRAVITY_TOP: sideRelative=rootTop; break; case GRAVITY_BOTTOM: sideRelative=rootBottom; break; case GRAVITY_LEFT: sideRelative=rootLeft; break; case GRAVITY_RIGHT: sideRelative=rootRight; break; case GRAVITY_MIDDLE: sideRelative=rootMiddle; break; } }else { sideRelative=0; } } private void drawTimeLine(Canvas canvas) { int childCount = getChildCount(); if (childCount > 0) { //大于1,证明至少有2个,也就是第一个和第二个之间连成线,第一个和最后一个分别有点/icon if (childCount > 1) { switch (curOrientation) { case VERTICAL: drawFirstChildViewVertical(canvas); drawLastChildViewVertical(canvas); drawBetweenLineVertical(canvas,childCount); break; case HORIZONTAL: drawFirstChildViewHorizontal(canvas); drawLastChildViewHorizontal(canvas); drawBetweenLineHorizontal(canvas); break; default: break; } } else if (childCount == 1) { switch (curOrientation) { case VERTICAL: drawFirstChildViewVertical(canvas); break; case HORIZONTAL: drawFirstChildViewHorizontal(canvas); break; default: break; } } } } //=============================================================Vertical Draw private void drawFirstChildViewVertical(Canvas canvas) { if (getChildAt(0) != null) { int top = getChildAt(0).getTop(); //记录值 lastX = (sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide)) - (mIcon .getWidth() >> 1); lastY = top + getChildAt(0).getPaddingTop() + lineDynamicDimen; //画一个图 canvas.drawBitmap(mIcon, lastX, lastY, null); } } private void drawLastChildViewVertical(Canvas canvas) { if (getChildAt(getChildCount()-1) != null) { int top = getChildAt(getChildCount()-1).getTop(); //记录值 firstX = sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide); firstY = top + getChildAt(getChildCount()-1).getPaddingTop() + lineDynamicDimen; //画一个圆 canvas.drawCircle(firstX, firstY, pointSize, pointPaint); } } private void drawBetweenLineVertical(Canvas canvas, int childCount) { //画剩下的 for (int i = 0; i < getChildCount() - 1; i++) { //画了线,就画圆 if (getChildAt(i) != null && i != 0) { int top = getChildAt(i).getTop(); int Y = top + getChildAt(i).getPaddingTop() + lineDynamicDimen; //记录值 canvas.drawCircle(firstX, Y, pointSize, pointPaint); } // if (i==0){ canvas.drawLine(firstX, firstY, firstX, lastY+mIcon.getHeight(), linePaint); // // }else { // canvas.drawLine(firstX, firstY, firstX, lastY, linePaint); // } } } //=============================================================Horizontal Draw private void drawFirstChildViewHorizontal(Canvas canvas) { if (getChildAt(0) != null) { int left = getChildAt(0).getLeft(); //记录值 firstX = left + getChildAt(0).getPaddingLeft() + lineDynamicDimen; firstY = sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide); //画一个圆 canvas.drawCircle(firstX, firstY, pointSize, pointPaint); } } private void drawLastChildViewHorizontal(Canvas canvas) { if (getChildAt(getChildCount() - 1) != null) { int left = getChildAt(getChildCount() - 1).getLeft(); //记录值 lastX = left + getChildAt(getChildCount() - 1).getPaddingLeft() + lineDynamicDimen; lastY = (sideRelative>=rootMiddle?(sideRelative-lineMarginSide):(sideRelative+lineMarginSide)) - (mIcon .getWidth() >> 1); //画一个图 canvas.drawBitmap(mIcon, lastX, lastY, null); } } private void drawBetweenLineHorizontal(Canvas canvas) { //画剩下的线 canvas.drawLine(firstX, firstY, lastX, firstY, linePaint); for (int i = 0; i < getChildCount() - 1; i++) { //画了线,就画圆 if (getChildAt(i) != null && i != 0) { int left = getChildAt(i).getLeft(); //记录值 int x = left + getChildAt(i).getPaddingLeft() + lineDynamicDimen; canvas.drawCircle(x, firstY, pointSize, pointPaint); } } } //=============================================================Getter/Setter @Override public void setOrientation(int orientation) { super.setOrientation(orientation); this.curOrientation = orientation; invalidate(); } public int getLineStrokeWidth() { return lineStrokeWidth; } public void setLineStrokeWidth(int lineStrokeWidth) { this.lineStrokeWidth = lineStrokeWidth; invalidate(); } public boolean isDrawLine() { return drawLine; } public void setDrawLine(boolean drawLine) { this.drawLine = drawLine; invalidate(); } public Paint getLinePaint() { return linePaint; } public void setLinePaint(Paint linePaint) { this.linePaint = linePaint; invalidate(); } public int getPointSize() { return pointSize; } public void setPointSize(int pointSize) { this.pointSize = pointSize; invalidate(); } public int getPointColor() { return pointColor; } public void setPointColor(int pointColor) { this.pointColor = pointColor; invalidate(); } public Paint getPointPaint() { return pointPaint; } public void setPointPaint(Paint pointPaint) { this.pointPaint = pointPaint; invalidate(); } public int getLineColor() { return lineColor; } public void setLineColor(int lineColor) { this.lineColor = lineColor; invalidate(); } public int getLineMarginSide() { return lineMarginSide; } public void setLineMarginSide(int lineMarginSide) { this.lineMarginSide = lineMarginSide; invalidate(); } public int getLineDynamicDimen() { return lineDynamicDimen; } public void setLineDynamicDimen(int lineDynamicDimen) { this.lineDynamicDimen = lineDynamicDimen; invalidate(); } public Bitmap getIcon() { return mIcon; } public void setIcon(Bitmap icon) { mIcon = icon; } public void setIcon(int resId) { if (resId == 0) return; BitmapDrawable temp = (BitmapDrawable) mContext.getResources().getDrawable(resId); if (temp != null) mIcon = temp.getBitmap(); invalidate(); } public int getLineGravity() { return lineGravity; } public void setLineGravity(int lineGravity) { this.lineGravity = lineGravity; invalidate(); } }同时还有一个style需要你添加
<!-- 时间轴 --> <declare-styleable name="UnderLineLinearLayout"> <!--时间轴偏移值--> <attr name="line_margin_side" format="dimension"/> <!--时间轴动态调整值--> <attr name="line_dynamic_dimen" format="dimension"/> <!--线宽--> <attr name="line_stroke_width" format="dimension"/> <!--线的颜色--> <attr name="line_v_color" format="color"/> <!--点的大小--> <attr name="point_size" format="dimension"/> <!--点的颜色--> <attr name="point_color" format="color"/> <!--图标--> <attr name="icon_src" format="reference"/> <!--时间轴的gravity--> <!--the gravity of the timeline--> <attr name="line_gravity"> <enum name="Left" value="2"/> <enum name="Right" value="4"/> <enum name="Middle" value="0"/> <enum name="Top" value="1"/> <enum name="Bottom" value="3"/> </attr> </declare-styleable>中间又一个R.drawable.point,就是节点颜色的一个xml,你自己随便定义一个就好
使用的话布局文件如下
<ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="45dp" android:paddingTop="20px" android:paddingBottom="20px" android:orientation="vertical" android:background="@color/white"> <TextView android:id="@+id/txt_logistics_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:textSize="@dimen/text_normal_medium" android:textColor="@color/xml_color_text_normal_black" android:text="物流状态: "/> <TextView android:id="@+id/txt_logistics_company" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginTop="10px" android:gravity="center" android:textSize="@dimen/text_normal_medium" android:textColor="@color/xml_color_text_normal_gray" android:text="承运公司: "/> <TextView android:id="@+id/txt_logistics_order_id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginTop="10px" android:gravity="center" android:textSize="@dimen/text_normal_medium" android:textColor="@color/xml_color_text_normal_gray" android:text="运单编号: "/> <TextView android:id="@+id/txt_logistics_phone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginTop="10px" android:gravity="center" android:textSize="@dimen/text_normal_medium" android:textColor="@color/xml_color_text_normal_gray" android:text="官方电话: "/> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1px" android:background="@color/xml_color_text_normal_gray_cd"/> <View android:layout_width="match_parent" android:layout_height="@dimen/space_normal_dp" android:background="@color/xml_color_text_normal_gray_bg"/> <View android:layout_width="match_parent" android:layout_height="1px" android:background="@color/xml_color_text_normal_gray_cd"/> <UnderLineLinearLayout android:id="@+id/underline_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@android:color/white" app:line_margin_side="35dp" app:point_size="@dimen/space_6" app:line_stroke_width="@dimen/space_3" app:point_color="@color/xml_color_text_normal_gray_bg" app:line_v_color="@color/xml_color_text_normal_gray_bg" app:line_dynamic_dimen="8dp"/> </LinearLayout> </ScrollView> //初始化物流时间轴 private void initBody(List<ExpressInfoEntity.ResultBean.ListBean> list){ for (int i = 0; i < list.size(); i++) { View v = LayoutInflater.from(this).inflate(R.layout.logicst_item, underlineLayout, false); ((TextView) v.findViewById(R.id.tx_action)).setText(list.get(i).getStatus()); ((TextView) v.findViewById(R.id.tx_time)).setText(list.get(i).getTime()); if (i==0){ ((TextView) v.findViewById(R.id.tx_action)).setTextColor(ContextCompat.getColor(this,R.color.xml_color_text_normal_style)); ((TextView) v.findViewById(R.id.tx_time)).setTextColor(ContextCompat.getColor(this,R.color.xml_color_text_normal_style)); } underlineLayout.addView(v); } }