最近修改项目遇到查看物流这个需求,经过一个下午的时间的终于搞定,趁着这个时间点,赶快把这个功能抽取出来,方便大家以后开发的需要,帮助到更多的人
先看效果图,如下
看完之后,分析可知道,主要是两部分,一个头部和一个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"/>
<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.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);
}
}