效果图
初步思路
自定义控件是必须的,看效果首先想到的就是继承
LinearLayout
。
View的排布,LinearLayout已经帮我们搞定了,我们需要做的就是把左边的时间线和状态圆点给画出来。
至于绘画的状态又有两种,一种是普通状态,一种是高亮状态。这时候我们可以提供当前View的位置信息,抽象出接口,把状态的选择权让给用户。
时间点
:通过对应的View的getY() + getHeight()/2
就可以获取对应的时间点位置。
时间线
:我们只需要记录第一个View和最后一个View对应的时间点位置就可以画出来了
小细节
绘图的过程中有许多细节需要去注意
- 自定义
ViewGroup
控件必须提供背景色,否则不会调用onDraw()
方法 - 画点跟画线的顺序必须是先画线,再画点,否则点上面就会被线给遮盖部分。
代码实现
TimeLineView.java
package demo.august1996.top.timelineviewdemo.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;
/**
* Created by August on 16/8/20.
*/
public class TimeLineView extends LinearLayout {
//高亮图片
private Bitmap mIcon;
//时间线左右边距
private int leftAndRightMargin = dp2px(30);
//第一个点的坐标
private int firstX;
private int firstY;
//最后一个点的坐标
private int lastX;
private int lastY;
//时间线画笔、颜色、粗细
private Paint linePaint;
private int lineColor = Color.parseColor("#ff4ed6b4");
private int lineStoke = dp2px(2);
//时间点画笔
private Paint pointPaint;
private int pointColor = Color.parseColor("#ff4ed6b4");
private int pointRadius = dp2px(5);
//提供当前位置是否为高亮状态
private HeightLineProvider mHeightLineProvider;
public interface HeightLineProvider {
boolean isHeightLine(int position);
}
public TimeLineView(Context context) {
this(context, null);
}
public TimeLineView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TimeLineView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
prePare();
}
/**
* 初始化
*/
private void prePare() {
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(lineColor);
linePaint.setStrokeWidth(lineStoke);
linePaint.setStyle(Paint.Style.FILL);
pointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
pointPaint.setColor(pointColor);
pointPaint.setDither(true);
pointPaint.setStyle(Paint.Style.FILL);
setOrientation(VERTICAL);
}
/**
* 设置供用户选择是否高亮的接口
*
* @param provider
*/
public void setHeightLineProvider(HeightLineProvider provider) {
this.mHeightLineProvider = provider;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawTimeLine(canvas);
}
/**
* 绘画时间线
*
* @param canvas
*/
private void drawTimeLine(Canvas canvas) {
//没有子View,不用绘画
if (getChildCount() == 0)
return;
//计算起点和终点位置,并且画出所有点
countFirstPoint();
countLastPoint();
drawJoinLine(canvas);
drawPoint(canvas);
}
/**
* 计算起点位置
*/
private void countFirstPoint() {
firstX = leftAndRightMargin;
View view = getChildAt(0);
firstY = (int) (view.getY() + view.getHeight() / 2);
}
/**
* 计算终点位置
*/
private void countLastPoint() {
lastX = leftAndRightMargin;
View lastView = getChildAt(getChildCount() - 1);
lastY = (int) (lastView.getY() + lastView.getHeight() / 2);
}
/**
* 画线
* @param canvas
*/
private void drawJoinLine(Canvas canvas) {
countLastPoint();
canvas.drawLine(firstX, firstY, lastX, lastY, linePaint);
}
/**
* 画出点
* @param canvas
*/
private void drawPoint(Canvas canvas) {
for (int i = 0; i < getChildCount(); i++) {
int x = leftAndRightMargin;
View view = getChildAt(i);
int y = (int) (view.getY() + view.getHeight() / 2);
/**
* 如果用户设置了选择状态的权利并且返回真并且设置了高亮图片,则画出高亮状态,否则画出正常状态
*/
if (mHeightLineProvider != null && mHeightLineProvider.isHeightLine(i) && mIcon != null) {
drawHeightLine(canvas, x, y);
} else {
drawNorPoint(canvas, x, y);
}
}
}
/**
* 画正常点
* @param canvas
* @param x
* @param y
*/
private void drawNorPoint(Canvas canvas, int x, int y) {
canvas.drawCircle(x, y, pointRadius, pointPaint);
}
/**
* 画出高亮点
* @param canvas
* @param x
* @param y
*/
private void drawHeightLine(Canvas canvas, int x, int y) {
canvas.drawBitmap(mIcon, x - mIcon.getWidth() / 2, y - mIcon.getHeight() / 2, null);
}
/**
* dp转px
* @param dp
* @return
*/
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
}
/**
* 设置高亮点的图片
* @param bitmap
*/
public void setHeightBitmap(Bitmap bitmap) {
this.mIcon = bitmap;
}
}
item_test.xml
activity_main.xml
MainActivity.java
package demo.august1996.top.timelineviewdemo;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
import demo.august1996.top.timelineviewdemo.widget.TimeLineView;
public class MainActivity extends AppCompatActivity implements TimeLineView.HeightLineProvider {
@Override
public boolean isHeightLine(int position) {
if (position % 4 == 0) {
return true;
}
return false;
}
private TimeLineView tlv;
private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tlv = (TimeLineView) findViewById(R.id.tlv);
tlv.setHeightLineProvider(this);
BitmapDrawable bitmapDrawable = (BitmapDrawable) getResources().getDrawable(R.drawable.ic_ok);
tlv.setHeightBitmap(bitmapDrawable.getBitmap());
}
private void addItem() {
View v = LayoutInflater.from(this).inflate(R.layout.item_test, tlv, false);
((TextView) v.findViewById(R.id.tx_action)).setText(tlv.getChildCount() + "探访了你");
((TextView) v.findViewById(R.id.tx_action_time)).setText(sdf.format(new Date()));
((TextView) v.findViewById(R.id.tx_action_status)).setText("查看");
tlv.addView(v);
}
public void addView(View v) {
addItem();
}
public void removeView(View v) {
if (tlv.getChildCount() != 0) {
tlv.removeViewAt(tlv.getChildCount() - 1);
}
}
}
源码
Github源码