之前在进阶篇我们讲解了自定义分割线和item的点击事件
传送门 :RecyclerView进阶篇
在那篇的最后提到了时间轴,这个效果我们经常会看到,比如查看快递动态的时候
这就是所谓的时间轴
这里我就实现一个和这不太一样的时间轴效果,它长成这个样子
它是一个路线走向图,有点像地铁上的路线效果。小车所在的位置表示你目前所处的位置,未经过的点是绿色,经过的点变成红色。当然我这里就只能通过点击item来表示经过这个点了。比如上图是我点击了信息楼的效果。
一、简易设计图
设计图很简单,划分两部分,时间轴区域和RecyclerView的item区域。
item的区域划分了三部分,起点,中间点和终点,其实不划分也是可以实现效果图的。但是划分之后有个好处就是:可扩展性变强了,比如对于Head起点部分我想突出一点,通过图片或其他方式展现,不想和body部分一样。所以我划分了三个部分。这部分的实现我在入门篇详细介绍过了。这里有传送门:RecyclerView入门篇
这里我就贴下代码跳过了,想看本节重点的也可以划过这段代码,直接看后面内容哈
public class SpotInfoAdapter extends RecyclerView.Adapter {
List spotInfoList =new ArrayList<>(); //数据集合
private OnItemClickListener mOnItemClickListener;
private static final int HEADER_TYPE=0; //头
private static final int FOOTER_TYPE=-1; //尾
public SpotInfoAdapter(List spotInfoList){
this.spotInfoList = spotInfoList; //数据初始化
}
//实例化停车点信息类
private SpotInfo mSpotInfo=new SpotInfo();
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
if (viewType==HEADER_TYPE){
return createHeaderViewHolder(viewGroup);
}else if (viewType==FOOTER_TYPE){
return createFooterViewHolder(viewGroup);
}
else {
//构建身体部分viewholder
return createBodyViewHolder(viewGroup);
}
}
private RecyclerView.ViewHolder createFooterViewHolder(ViewGroup viewGroup) {
View footerView= LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.spot_footer,viewGroup,false);
return new FooterViewHolder(footerView);
}
private BodyViewHolder createBodyViewHolder(ViewGroup viewGroup) {
//2.实例化子布局
View itemView= LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.spot_body,viewGroup,false);
//3.获得一个ViewHolder实例
return new BodyViewHolder(itemView);
}
private HeaderViewHolder createHeaderViewHolder(ViewGroup viewGroup) {
View headerView=LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.spot_header,viewGroup,false);
return new HeaderViewHolder(headerView);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof BodyViewHolder){
//身体绑定到view中
bindViewForBody(holder,position);
}else if(holder instanceof HeaderViewHolder){
//绑定头
bindViewForHeader(holder,position);
}else if (holder instanceof FooterViewHolder){
//绑定尾部
bindViewForFooter(holder,position);
}
}
private void bindViewForFooter(RecyclerView.ViewHolder holder,int position) {
FooterViewHolder footerViewHolder=(FooterViewHolder)holder;
mSpotInfo=getItem(position);
footerViewHolder.tv_FootSpotName.setText("终点:"+mSpotInfo.getAddressName());
}
private void bindViewForHeader(RecyclerView.ViewHolder holder,int position) {
HeaderViewHolder headerViewHolder=(HeaderViewHolder) holder;
mSpotInfo=getItem(position);
headerViewHolder.tv_HeadSpotName.setText("起点:"+mSpotInfo.getAddressName());
}
private void bindViewForBody(final RecyclerView.ViewHolder holder, final int position) {
BodyViewHolder newsViewHolder=(BodyViewHolder) holder;
mSpotInfo=getItem(position);
//将数据填充进去
newsViewHolder.tv_BodySpotName.setText(mSpotInfo.getAddressName());
//点击事件
newsViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener!=null){
mOnItemClickListener.onClick(mSpotInfo,position);
}
}
});
//长按点击事件
newsViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos=holder.getLayoutPosition();
mOnItemClickListener.onItemLongClick(pos);
return false;
}
});
}
@Override
public int getItemCount() {
//return spotInfoList.size();
return spotInfoList ==null?0: spotInfoList.size(); //计算position的数目
}
protected SpotInfo getItem(int position){
//6.获取每个item的内容
return spotInfoList.get(position);
}
//1.初始化自己的ViewHolder
static class BodyViewHolder extends RecyclerView.ViewHolder {
public TextView tv_BodySpotName;
public BodyViewHolder(View itemView) {
super(itemView);
//获取子布局的控件实例
tv_BodySpotName=(TextView) itemView.findViewById(R.id.tv_bodySpotName);
}
}
//初始化头view
static class HeaderViewHolder extends RecyclerView.ViewHolder{
TextView tv_HeadSpotName;
public HeaderViewHolder(View itemView) {
super(itemView);
tv_HeadSpotName=(TextView) itemView.findViewById(R.id.tv_headSpotName);
}
}
//初始化尾view
static class FooterViewHolder extends RecyclerView.ViewHolder{
TextView tv_FootSpotName;
public FooterViewHolder(View itemView) {
super(itemView);
tv_FootSpotName=(TextView) itemView.findViewById(R.id.tv_lastSpotName);
}
}
@Override
public int getItemViewType(int position) {
if(HEADER_TYPE==position){
return 0;
}else if(spotInfoList.size()-1==position){
return -1;
} else {
return 1;
}
}
public void setOnClickListener(OnItemClickListener mOnClickListener){
this.mOnItemClickListener=mOnClickListener;
}
public interface OnItemClickListener{
void onClick(T item,int pos);
void onItemLongClick(int item);
}
二、划重点
接下来就是本次要说的重点啦--画时间轴。
1.首先和自定义分割线一样,先继承RecyclerView.ItemDecoration类
前面说了要划出左小半部分,这就需要重写getItemOffsets方法了,并设置outRect.left的偏移量(默认为0)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.left=(int) mOffsetLeft;
}
2.准备一些画笔和图标对象
public TimeLineItemDecoration(Context context){
mPaint=new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(10);
mPaint.setStyle(Paint.Style.FILL);
//文字画笔
mTextPaint=new Paint();
mTextPaint.setTextSize(45);
mTextPaint.setColor(Color.BLACK);
//未走的地点画笔
newPaint=new Paint();
newPaint.setColor(Color.GREEN);
newPaint.setStrokeWidth(10);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
//左边距
mOffsetLeft=250;
//圆形半径
mCircleRadius=50;
//使用bitmap缩略工具来改变图片的尺寸
bitmap= LoadBitmapUtil.decodeSampledBitmapFromResource(context.getResources(), R.drawable.car,120,120);
}
3.准备工作做好了就可以开始画了,重写onDraw方法
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount=parent.getChildCount();
......(省略的代码在后面逐渐展开)
}
4. 我们要在每个item左边画线,所以要遍历每一个子item,借助每个item的位置来计算我所画的轴线和文字的坐标。小车和不同颜色的圆需要加以判断才能画,如果在目前的位置(这里我定义为diffIndex,通过点击来更改这个diffIndex)画小车,小于这个值的画红圆,大于这个值的画绿圆
for (int i=0;idiffIndex) c.drawCircle(centerX,centerY,mCircleRadius,newPaint);
else c.drawCircle(centerX,centerY,mCircleRadius,mPaint);
//绘制下班部分的轴线
float downLineTopX=centerX;
float downLineTopY=centerY+mCircleRadius;
float downLineBottomX=centerX;
float downLineBottomY=dividerBottom;
if(index!=childCount-1)
c.drawLine(downLineTopX,downLineTopY,downLineBottomX,downLineBottomY,mPaint);
}
5. 最后在Activity里应用这个时间轴。并在每次点击后,调用notifyDataSetChanged方法重新绘制。
mSpotInfoAdapter.notifyDataSetChanged();
Activity代码如下:
public class MainActivity extends AppCompatActivity {
RecyclerView mRecyclerView;
SpotInfoAdapter mSpotInfoAdapter;
int diffIndex;
//时间轴对象
private TimeLineItemDecoration mTimeLine;
//停车地点
private String[] addressName={"文学楼","地信楼","生物楼","信息楼","体育馆"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.获取控件
mRecyclerView=(RecyclerView) findViewById(R.id.recycler_view);
//2.设置布局方式
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, OrientationHelper.VERTICAL,false)); //线性布局
mRecyclerView.setHasFixedSize(true);
//3.准备数据
List spotList=new ArrayList<>();
SpotInfo spots;
for(int i=0;i() {
@Override
public void onClick(SpotInfo item,int pos) {
Toast.makeText(MainActivity.this,"点击了其中一条"+addressName[pos],Toast.LENGTH_SHORT).show();
diffIndex=pos;
mTimeLine.setDiffIndex(diffIndex);
mSpotInfoAdapter.notifyDataSetChanged();
}
}
}
到这里就实现了我们想要的效果,现在再回头看就没有刚开始那么懵了,对整个实现过程就更清晰了。