美文网首页Android技术知识Android开发经验谈Android进阶之路
值得深入学习的控件-RecyclerView(时间轴篇)

值得深入学习的控件-RecyclerView(时间轴篇)

作者: 树獭非懒 | 来源:发表于2018-08-31 14:03 被阅读12次

    之前在进阶篇我们讲解了自定义分割线和item的点击事件

    传送门 :RecyclerView进阶篇

    在那篇的最后提到了时间轴,这个效果我们经常会看到,比如查看快递动态的时候

    time.jpg

    这就是所谓的时间轴

    这里我就实现一个和这不太一样的时间轴效果,它长成这个样子

    image.png

    它是一个路线走向图,有点像地铁上的路线效果。小车所在的位置表示你目前所处的位置,未经过的点是绿色,经过的点变成红色。当然我这里就只能通过点击item来表示经过这个点了。比如上图是我点击了信息楼的效果。

    一、简易设计图

    设计图很简单,划分两部分,时间轴区域和RecyclerView的item区域。

    时间轴设计图.jpg

    item的区域划分了三部分,起点,中间点和终点,其实不划分也是可以实现效果图的。但是划分之后有个好处就是:可扩展性变强了,比如对于Head起点部分我想突出一点,通过图片或其他方式展现,不想和body部分一样。所以我划分了三个部分。这部分的实现我在入门篇详细介绍过了。这里有传送门:RecyclerView入门篇

    这里我就贴下代码跳过了,想看本节重点的也可以划过这段代码,直接看后面内容哈

    public class SpotInfoAdapter extends RecyclerView.Adapter <RecyclerView.ViewHolder> {
    
        List<SpotInfo> spotInfoList =new ArrayList<>();  //数据集合
        private OnItemClickListener<SpotInfo> mOnItemClickListener;
    
        private static final int HEADER_TYPE=0;  //头
        private static final int FOOTER_TYPE=-1; //尾
        public SpotInfoAdapter(List<SpotInfo> 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<SpotInfo> mOnClickListener){
            this.mOnItemClickListener=mOnClickListener;
        }
    
        public  interface OnItemClickListener<T>{
            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;i<childCount;i++){
                View view=parent.getChildAt(i);
                int index=parent.getChildAdapterPosition(view);
                float  dividerTop=view.getTop()-mOffsetTop;
                float dividerLeft=parent.getPaddingLeft();
                float dividerBottom=view.getBottom();
                float dividerRight=parent.getWidth()-parent.getPaddingRight();
                
                   //圆心坐标(x,y)
                float centerX=dividerLeft+mOffsetLeft/2;
                float centerY=dividerTop+(dividerBottom-dividerTop)/2;
                
                 //绘制上半部分轴线
                float upLineTopX=centerX;
                float upLineTopY=dividerTop;
                float upLineBottomX=centerX;
                float upLineBottomY=centerY-mCircleRadius;
                if(index!=0)
                c.drawLine(upLineTopX,upLineTopY,upLineBottomX,upLineBottomY,mPaint);
                
                
                 //绘制轴线的文字
                float upTextX=upLineTopX+5;
                float upTextY=dividerBottom;
                String text="120米";
                if(index==diffIndex)
                c.drawText(text,upTextX,upTextY,mTextPaint);
                
                
                //在目前的位置的话就画小车,上面的部分用红画笔画圆,下面的部分用绿笔画圆
                if(index==diffIndex)
                    c.drawBitmap(bitmap,centerX-mCircleRadius,centerY-mCircleRadius,mPaint);
                else if(index>diffIndex) 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<SpotInfo> spotList=new ArrayList<>();
            SpotInfo spots;
            for(int i=0;i<addressName.length;i++){
                spots=new SpotInfo();
                spots.setAddressName(addressName[i]);
                spotList.add(spots);
            }
    
            //3.设置适配器
            mSpotInfoAdapter=new SpotInfoAdapter(spotList);
            //设置分割线
            //mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
            // mRecyclerView.addItemDecoration(new ColorDividerItemDecoration(Color.RED,5,LinearLayoutManager.VERTICAL));
            //设置事件轴对象
            mTimeLine=new TimeLineItemDecoration(this);
            diffIndex=0;
            mTimeLine.setDiffIndex(diffIndex);
            mRecyclerView.addItemDecoration(mTimeLine);
            mRecyclerView.setAdapter(mSpotInfoAdapter);
            mSpotInfoAdapter.setOnClickListener(new SpotInfoAdapter.OnItemClickListener<SpotInfo>() {
                @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();
                }
        }
    }
    

    到这里就实现了我们想要的效果,现在再回头看就没有刚开始那么懵了,对整个实现过程就更清晰了。

    相关文章

      网友评论

        本文标题:值得深入学习的控件-RecyclerView(时间轴篇)

        本文链接:https://www.haomeiwen.com/subject/wsmswftx.html