【Android 自定义 View】--> 单列时间轴(一)

不断学习,做更好的自己!💪

视频号CSDN简书
欢迎打开微信,关注我的视频号:KevinDev点我点我

效果图

01.png

基类代码

适配器

BaseAdapter.java

/**
 * Created on 2021/7/16 14:50
 *
 * @author Gong Youqiang
 */
@SuppressWarnings("ALL")
public abstract class BaseAdapter<Data> extends RecyclerView.Adapter<BaseAdapter.ViewHolder<Data>>
        implements View.OnClickListener,View.OnLongClickListener,IAdapterProxy<Data>{

    // 数据集合
    protected List<Data> mDataList;
    // 监听器
    private AdapterListener<Data> adapterListener;

    public BaseAdapter() {
        this(null);
    }

    public BaseAdapter(AdapterListener<Data> adapterListener) {
        this(new ArrayList<Data>(),adapterListener);
    }

    public BaseAdapter(List<Data> mDataList, AdapterListener<Data> adapterListener) {
        this.mDataList = mDataList;
        this.adapterListener = adapterListener;
    }

    @Override
    public ViewHolder<Data> onCreateViewHolder(ViewGroup parent, int viewType) {
        // 创建ViewHolder
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View root = inflater.inflate(viewType,parent,false);
        ViewHolder<Data> viewHolder = onCreateViewHolder(root,viewType);

        // 基础的操作
        root.setTag(R.id.recycler_view,viewHolder);
        root.setOnClickListener(this);
        root.setOnLongClickListener(this);
        doWithRoot(viewHolder,root);

        return viewHolder;
    }

    protected void doWithRoot(ViewHolder viewHolder,View root){

    }

    /**
     *  实际的创建ViewHolder的方法
     */
    public abstract ViewHolder<Data> onCreateViewHolder(View root, int viewType);

    @Override
    public void onBindViewHolder(ViewHolder<Data> holder, int position) {
        // 设置不能进行重复绘制
        //holder.setIsRecyclable(false);
        // TODO 对多数据进行测试,查看哪里出了问题
        // 绑定数据
        Data data = mDataList.get(position);
        holder.bind(data);
    }

    @Override
    public int getItemViewType(int position) {
        Data data = mDataList.get(position);
        return getItemLayout(data,position);
    }

    /**
     * 得到子布局的ID 适合多种子布局的情况下使用
     */
    public abstract int getItemLayout(Data data,int position);

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    @Override
    public void onClick(View v) {
        ViewHolder<Data> holder = (ViewHolder<Data>) v.getTag(R.id.recycler_view);
        if(holder != null){
            if(adapterListener == null)
                return;
            int pos = holder.getAdapterPosition();
            adapterListener.onItemClick(holder,mDataList.get(pos));
        }
    }

    @Override
    public boolean onLongClick(View v) {
        ViewHolder<Data> holder = (ViewHolder<Data>) v.getTag(R.id.recycler_view);
        if(holder != null){
            if(adapterListener != null){
                int pos = holder.getAdapterPosition();
                adapterListener.onItemLongClick(holder,mDataList.get(pos));
                return true;
            }
        }
        return false;
    }

    /**
     *  得到数据
     */
    public List<Data> getItems() {
        return mDataList;
    }

    /**
     * 新增一个数据
     */
    public void add(Data data){
        mDataList.add(data);
        notifyItemInserted(mDataList.size() -1 );
    }

    /**
     * 新增所有的数据
     */
    public void addAllData(Collection<Data> datas){
        int start = mDataList.size();
        mDataList.addAll(datas);
        notifyItemRangeChanged(start,datas.size());
    }

    /**
     *  新增所有的数组数据
     */
    public void addAllData(Data... datas){
        int start = mDataList.size();
        mDataList.addAll(Arrays.asList(datas));
        notifyItemRangeChanged(start,datas.length);
    }

    /**
     * 删除所有的数据
     */
    public void remove(){
        mDataList.clear();
        notifyDataSetChanged();
    }

    /**
     * 替换数据
     */
    public void replace(Collection<Data> datas){
        mDataList.clear();
        mDataList.addAll(datas);
        notifyDataSetChanged();
    }

    public void setAdapterListener(AdapterListener<Data> listener){
        this.adapterListener = listener;
    }


    /*
     * 适配器的监听器
     */
    public interface AdapterListener<Data>{
        // 单击的时候
        void onItemClick(ViewHolder<Data> holder, Data data);
        // 长按的时候
        void onItemLongClick(ViewHolder<Data> holder, Data data);
    }

    /**
     *  自定义的ViewHolder
     */
    public static abstract class ViewHolder<Data> extends RecyclerView.ViewHolder{
        protected Data mData;

        public ViewHolder(View itemView) {
            super(itemView);
        }

        public void bind(Data data){
            mData = data;
            onBind(data);
        }

        /**
         * 实现数据的绑定
         */
        protected abstract void onBind(Data data);
    }

    public abstract static class AdapterListenerImpl<Data> implements AdapterListener<Data>{
        @Override
        public void onItemClick(ViewHolder<Data> holder,Data data) {

        }

        @Override
        public void onItemLongClick(ViewHolder<Data> holder,Data data) {

        }
    }
}

IAdapterProxy.java

/**
 * Created on 2021/7/16 14:52
 *
 * @author Gong Youqiang
 */
public interface IAdapterProxy<Data>{
    void addAllData(Collection<Data> dataList);
    void setAdapterListener(BaseAdapter.AdapterListener<Data> listener);
}
2. 数据

ITimeItem.java

/**
 * Created on 2021/7/16 14:56
 *  时间轴数据需要实现的接口
 * @author Gong Youqiang
 */
public interface ITimeItem {
    /**
     * 构建绘制的标题
     * @return 标题
     */
    String getTitle();

    /**
     * 用户绘制原点的颜色
     * @return 颜色
     */
    int getColor();

    /**
     * 图片的资源文件
     * @return drawable的资源地址
     */
    int getResource();
}
3. ItemDecoration

TimeLine.java

/**
 * Created on 2021/7/16 14:54
 *
 * @author Gong Youqiang
 */
public abstract class TimeLine extends RecyclerView.ItemDecoration {
    // 标题
    public static final int FLAG_TITLE_POS_NONE = 0X0001;
    public static final int FLAG_TITLE_TYPE_TOP = 0x0002;
    public static final int FLAG_TITLE_TYPE_LEFT = 0x0004;
    public static final int FLAG_TITLE_DRAW_BG = 0x0008;

    public static final int FLAG_SAME_TITLE_HIDE = 0x0100;

    // 时间线
    public static final int FLAG_LINE_DIVIDE = 0x0010;
    public static final int FLAG_LINE_CONSISTENT = 0x0020;
    public static final int FLAG_LINE_BEGIN_TO_END= 0x0040;
    // 时间点
    public static final int FLAG_DOT_RES = 0x1000;
    public static final int FLAG_DOT_DRAW = 0x2000;


    protected Context mContext;
    protected List<? extends ITimeItem> timeItems;
    // 标题放置的类型
    protected int mFlag;
    // 上次的标题

    // 标题分两种,
    // 1. 上方
    // 2. 左侧
    protected int mTitleColor;
    protected int mTopOffset;
    protected int mLeftOffset;
    protected Paint mTextPaint;
    protected int mTitleFontSize;
    protected int mBgColor;
    protected Paint mBgPaint;

    // 线
    protected int mLineColor;
    protected Paint mLinePaint;
    protected int mLineOffset;
    protected int mLineWidth;

    // 点
    protected Paint mDotPaint;

    public TimeLine(Config config) {
        mContext = config.context;
        this.timeItems = config.timeItems;
        this.mFlag = config.flag;

        // 标题
        this.mTitleColor = config.titleColor;
        if ((mFlag & FLAG_TITLE_TYPE_TOP) != 0) {
            mTopOffset = DisplayUtils.dip2px(config.titleOffset);
        } else if ((mFlag & FLAG_TITLE_TYPE_LEFT) != 0) {
            mLeftOffset = DisplayUtils.dip2px(config.titleOffset);
        }
        this.mTitleFontSize = DisplayUtils.sp2px(mContext, config.titleFontSize);
        this.mBgColor = config.bgColor;

        // 时间线
        this.mLineColor = config.lineColor;
        this.mLineOffset = DisplayUtils.dip2px(config.lineOffset);
        this.mLineWidth = DisplayUtils.dip2px(config.lineWidth);

        init();
    }

    private void init() {
        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mLinePaint.setColor(mLineColor);

        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mTextPaint.setTextSize(mTitleFontSize);
        mTextPaint.setColor(mTitleColor);
        mBgPaint = new Paint();
        mBgPaint.setColor(mBgColor);

        mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    }


    /**
     * 更新部分数据
     */
    public void addItems(List items){
        this.timeItems.addAll(items);
    }

    /**
     * 更新全部数据
     * @param items 数据
     */
    public void replace(List<? extends ITimeItem> items){
        this.timeItems = items;
    }

    /**
     * 清除数据
     */
    public void remove(){
        this.timeItems.clear();
    }

    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);

        // 兼容4.0硬件加速无效
        parent.setLayerType(View.LAYER_TYPE_SOFTWARE,mDotPaint);

        int childCount = parent.getChildCount();
        if (childCount == 0)
            return;

        // 绘制处理
        // 1. 绘制标题
        drawTitle(c, parent);
        // 2. 绘制线
        drawVerticalLine(c, parent);
        // 3. 绘制点
        drawPoint(c, parent);
    }

    /**
     * 绘制标题
     */
    protected abstract void drawTitle(Canvas canvas, RecyclerView parent);

    /**
     * 绘制直线
     * @param c Canvas
     * @param parent RecyclerView
     */
    protected abstract void drawVerticalLine(Canvas c, RecyclerView parent);

    /**
     * 绘制点
     * @param c Canvas
     * @param parent RecyclerView
     */
    protected abstract void drawPoint(Canvas c, RecyclerView parent);


    public static class Config {
        Context context;
        List<? extends ITimeItem> timeItems = new ArrayList<>();
        int flag = 0;
        // 标题
        int titleColor = Color.parseColor("#4e5864");
        int titleFontSize = 20;
        int bgColor;
        int titleOffset = 40;
        // 线
        int lineColor = Color.parseColor("#8d9ca9");
        int lineOffset = 30;
        int lineWidth = 1;
    }

    public static class Builder {
        private Config mConfig;

        public Builder(Context context) {
            this(context,new ArrayList());
        }

        public Builder(Context context,List<? extends ITimeItem> timeItems) {
            this.mConfig = new Config();
            this.mConfig.context = context;
            this.mConfig.timeItems = timeItems;
        }

        /**
         * 设置标题
         *
         * @param titleColor 标题文本的颜色
         * @param fontSize   标题文本的大小 dp
         * @param bgColor    背景颜色
         */
        public Builder setTitle(int titleColor, int fontSize, int bgColor) {
            this.mConfig.titleColor = titleColor;
            this.mConfig.titleFontSize = fontSize;
            this.mConfig.bgColor = bgColor;
            this.mConfig.flag |= FLAG_TITLE_DRAW_BG;
            return this;
        }

        /**
         * 设置标题
         *
         * @param titleColor 标题文本的颜色
         * @param fontSize   标题文本的大小 dp
         */
        public Builder setTitle(int titleColor, int fontSize) {
            this.mConfig.titleColor = titleColor;
            this.mConfig.titleFontSize = fontSize;
            return this;
        }

        /**
         * 可以设置Title的位置,比如将标题设置在顶部或者将标题设置左边
         *
         * @param type        类型  FLAG_TITLE_POS_NONE/FLAG_TITLE_TYPE_TOP/FLAG_TITLE_TYPE_LEFT
         * @param titleOffset 偏移量
         */
        public Builder setTitleStyle(int type, int titleOffset) {
            this.mConfig.flag |= type;
            this.mConfig.titleOffset = titleOffset;
            return this;
        }

        /**
         * 启动隐藏相同标题
         */
        public Builder setSameTitleHide() {
            this.mConfig.flag |= FLAG_SAME_TITLE_HIDE;
            return this;
        }

        /**
         * @param type       type 时间线的类型
         * @param lineOffset 时间轴左边偏移的大小,右边也会偏移同样的大小
         */
        public Builder setLine(int type, int lineOffset, int lineColor) {
            return setLine(type, lineOffset, lineColor,1);
        }

        /**
         * @param type       type 时间线的类型
         * @param lineOffset 时间轴左边偏移的大小,右边也会偏移同样的大小
         */
        public Builder setLine(int type, int lineOffset, int lineColor,int lineWidth) {
            this.mConfig.flag |= type;
            this.mConfig.lineOffset = lineOffset;
            this.mConfig.lineColor = lineColor;
            this.mConfig.lineWidth = lineWidth;
            return this;
        }

        /**
         * 设置原点
         *
         * @param type   点的类型
         */
        public Builder setDot(int type) {
            this.mConfig.flag |= type;
            return this;
        }

        /**
         * 构建
         *
         * @param cls 构建的类
         * @return T
         */
        public TimeLine build(Class<? extends TimeLine> cls) {
            TimeLine t = null;
            try {
                Constructor<? extends TimeLine> con = cls.getConstructor(Config.class);
                t = con.newInstance(mConfig);
            } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
                e.printStackTrace();
            }
            return t;
        }

    }
}

SingleTimeLineDecoration.java

/**
 * Created on 2021/7/16 14:54
 *
 * @author Gong Youqiang
 */
public abstract class SingleTimeLineDecoration extends TimeLine {
    public SingleTimeLineDecoration(Config config) {
        super(config);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);

        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        int pos = params.getViewAdapterPosition();
        ITimeItem timeItem = timeItems.get(pos);

        if ((mFlag & FLAG_SAME_TITLE_HIDE) != 0) {
            if ((mFlag & FLAG_TITLE_TYPE_TOP) != 0) {
                if (pos == 0 || !timeItem.getTitle().equals(timeItems.get(pos - 1).getTitle())) {
                    outRect.set(mLineOffset + mLineWidth, mTopOffset, 0, 0);
                } else {
                    outRect.set(mLineOffset + mLineWidth, 0, 0, 0);
                }
            } else {
                outRect.set(mLineOffset + mLineWidth + mLeftOffset, 0, 0, 0);
            }
        } else {
            if ((mFlag & FLAG_TITLE_TYPE_TOP) != 0) {
                outRect.set(mLineOffset + mLineWidth, mTopOffset, 0, 0);
            } else {
                outRect.set(mLineOffset + mLineWidth + mLeftOffset, 0, 0, 0);
            }
        }
    }


    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);

        int childCount = parent.getChildCount();
        if (childCount == 0)
            return;

        // 绘制处理
        // 1. 绘制标题
        drawTitle(c, parent);
        // 2. 绘制线
        drawVerticalLine(c, parent);
        // 3. 绘制点
        drawPoint(c, parent);
    }

    /**
     * 绘制标题
     */
    @Override
    protected void drawTitle(Canvas canvas, RecyclerView parent) {
        int childCount = parent.getChildCount();

        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            // 适配器中的实际位置
            int pos = params.getViewAdapterPosition();
            ITimeItem timeItem = timeItems.get(pos);
            int mLeft, mTop, mRight, mBottom;

            // 绘制上侧 FLAG_TITLE_TYPE_TOP 和左侧 FLAG_TITLE_TYPE_LEFT略有不同
            // 1. 上侧
            // 绘制区域:
            //          上下:高度 = mTopOffset, 在子视图之上,间隔着子视图的topMargin的长度
            //          左右:宽度 = 子视图宽度 + 左右的margin长度
            // 2. 左侧
            // 绘制区域
            //          上下:高度 = 子视图高度
            //          左右:宽度 = 时间线偏移量 + 时间线的宽度 + 标题偏移量, 间隔着左margin的长度
            if ((mFlag & FLAG_SAME_TITLE_HIDE) != 0) {
                if (i == 0 && pos != 0) {
                    if (timeItem.getTitle().equals(timeItems.get(pos - 1).getTitle())) {
                        continue;
                    }
                }

                if (pos != 0 && timeItem.getTitle().equals(timeItems.get(pos - 1).getTitle())) {
                    continue;
                }


                if ((mFlag & FLAG_TITLE_TYPE_TOP) != 0) {
                    mLeft = parent.getPaddingLeft();
                    mTop = child.getTop() - params.topMargin - mTopOffset;
                    mRight = child.getRight() + params.rightMargin;
                    mBottom = child.getTop() - params.topMargin;
                    // 绘制背景
                    if ((mFlag & FLAG_TITLE_DRAW_BG) != 0)
                        canvas.drawRect(mLeft, mTop, mRight, mBottom, mBgPaint);
                    onDrawTitleItem(canvas, mLeft, mTop, mRight, mBottom, pos);
                } else if ((mFlag & FLAG_TITLE_TYPE_LEFT) != 0) {
                    mLeft = parent.getPaddingLeft();
                    mTop = child.getTop();
                    mRight = child.getLeft() - params.leftMargin - (mLineOffset + mLineWidth);
                    mBottom = child.getBottom();
                    if ((mFlag & FLAG_TITLE_DRAW_BG) != 0)
                        canvas.drawRect(mLeft, mTop, mRight, mBottom, mBgPaint);
                    onDrawTitleItem(canvas, mLeft, mTop, mRight, mBottom, pos);
                }
            } else {
                if ((mFlag & FLAG_TITLE_TYPE_TOP) != 0) {
                    // 绘制上面的标题
                    mLeft = parent.getPaddingLeft() + params.leftMargin;
                    mTop = child.getTop() - params.topMargin - mTopOffset;
                    mRight = child.getRight();
                    mBottom = child.getTop() - params.topMargin;
                    if ((mFlag & FLAG_TITLE_DRAW_BG) != 0)
                        canvas.drawRect(mLeft, mTop, mRight, mBottom, mBgPaint);
                    onDrawTitleItem(canvas, mLeft, mTop, mRight, mBottom, pos);
                } else if ((mFlag & FLAG_TITLE_TYPE_LEFT) != 0) {
                    mLeft = parent.getPaddingLeft();
                    mTop = child.getTop();
                    mRight = child.getLeft() - params.leftMargin - (mLineOffset + mLineWidth);
                    mBottom = child.getBottom();
                    if ((mFlag & FLAG_TITLE_DRAW_BG) != 0)
                        canvas.drawRect(mLeft, mTop, mRight, mBottom, mBgPaint);
                    onDrawTitleItem(canvas, mLeft, mTop, mRight, mBottom, pos);
                }
            }
        }
    }

    /**
     * 绘制标题
     *
     * @param left   绘制文本区域范围
     * @param top    绘制文本区域范围
     * @param right  绘制文本区域范围
     * @param bottom 绘制文本区域范围
     * @param pos    使用数据的位置
     */
    protected abstract void onDrawTitleItem(Canvas canvas, int left, int top, int right, int bottom, int pos);


    @Override
    protected void drawVerticalLine(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        int childCount = parent.getChildCount();

        // 绘制时间线的x坐标
        int beginX = left + mLineOffset / 2 + mLeftOffset;
        int endX = beginX + mLineWidth;
        int beginY = 0, endY = 0;

        if ((mFlag & FLAG_LINE_DIVIDE) != 0) {
            // 给相同的标题画 时间线
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
                int pos = params.getViewAdapterPosition();
                ITimeItem item = timeItems.get(pos);

                if (pos == 0 || !item.getTitle().equals(timeItems.get(pos - 1).getTitle()) || pos == timeItems.size() - 1
                        || i== childCount -1) {
                    if (pos == 0) {
                        beginY = (child.getTop() + child.getBottom()) / 2;
                    } else if (pos == timeItems.size() - 1) {
                        endY = (child.getTop() + child.getBottom()) / 2;
                        c.drawRect(beginX, beginY, endX, endY, mLinePaint);
                    } else if(!item.getTitle().equals(timeItems.get(pos - 1).getTitle())) {
                        //Log.e(SingleTimeLineDecoration.class.getSimpleName(),"i:"+i+",pos:"+pos);
                        View lastChild = parent.getChildAt(i - 1);
                        if(lastChild != null) {
                            endY = (lastChild.getTop() + lastChild.getBottom()) / 2;
                            if (endY != beginY) {
                                c.drawRect(beginX, beginY, endX, endY, mLinePaint);
                            }
                        }
                        beginY = (child.getTop() + child.getBottom()) / 2;
                    }else {
                        if(childCount == 1)
                            continue;

                        endY = child.getBottom();
                        c.drawRect(beginX, beginY, endX, endY, mLinePaint);
                    }
                }
            }
        } else if ((mFlag & FLAG_LINE_BEGIN_TO_END) != 0) {
            View lastChild = parent.getChildAt(childCount - 1);
            RecyclerView.LayoutParams lastParams = (RecyclerView.LayoutParams) lastChild.getLayoutParams();
            View firstChild = parent.getChildAt(0);
            RecyclerView.LayoutParams firstParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams();

            if (firstParams.getViewAdapterPosition() == 0) {
                beginY = (firstChild.getTop() + firstChild.getBottom()) / 2;
            } else {
                beginY = firstChild.getTop();
            }

            if (lastParams.getViewAdapterPosition() == timeItems.size() - 1) {
                endY = (lastChild.getBottom() + lastChild.getTop()) / 2;
            } else {
                endY = lastChild.getBottom();
            }

            c.drawRect(beginX, beginY, endX, endY, mLinePaint);
        } else {
            View lastChild = parent.getChildAt(childCount - 1);
            beginY = parent.getTop();
            endY = lastChild.getBottom();
            c.drawRect(beginX, beginY, endX, endY, mLinePaint);
        }


    }

    @Override
    protected void drawPoint(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            // 圆心坐标
            int cx, cy;
            int top = child.getTop();
            int bottom = child.getBottom();
            cy = (bottom + top) / 2;
            int r = (bottom - top) / 2;
            r = Math.min((mLineOffset + mLineWidth) / 2, r);

            if ((mFlag & FLAG_TITLE_POS_NONE) != 0 || (mFlag & FLAG_TITLE_TYPE_TOP) != 0) {
                cx = (mLineOffset + (mLineWidth + 1)) / 2;
            } else {
                cx = mLeftOffset + (mLineOffset + (mLineWidth + 1)) / 2;
            }

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int pos = params.getViewAdapterPosition();
            if ((mFlag & FLAG_DOT_RES) != 0) {
                ITimeItem timeItem = timeItems.get(pos);
                if (timeItem != null) {
                    Drawable drawable = ContextCompat.getDrawable(mContext, timeItem.getResource());
                    onDrawDotResItem(c, cx, cy, r, drawable, pos);
                }
            } else
                onDrawDotItem(c, cx, cy, r, pos);
        }
    }

    /**
     * 绘制原点
     *
     * @param cx     圆心x
     * @param cy     圆心y
     * @param radius 最大半径
     * @param pos    位置
     */
    protected void onDrawDotItem(Canvas canvas, int cx, int cy, int radius, int pos) {

    }

    /**
     * @param cx       圆心X
     * @param cy       圆心Y
     * @param radius   最大半径
     * @param drawable 绘制的Drawable
     * @param pos      位置
     */
    protected void onDrawDotResItem(Canvas canvas, int cx, int cy, int radius, Drawable drawable, int pos) {

    }

}

实例

第一步: 继承 SingleTimeLineDecoration

/**
 * Created on 2021/7/16 15:00
 *
 * @author Gong Youqiang
 */
public class StepSTL extends SingleTimeLineDecoration {
    private Paint mRectPaint;

    public StepSTL(SingleTimeLineDecoration.Config config) {
        super(config);

        mRectPaint = new Paint();
        mRectPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.SOLID));
        mDotPaint.setMaskFilter(new BlurMaskFilter(6, BlurMaskFilter.Blur.SOLID));
    }

    @Override
    protected void onDrawTitleItem(Canvas canvas, int left, int top, int right, int bottom, int pos) {
        ITimeItem item = timeItems.get(pos);

        int rectWidth = DisplayUtils.dip2px(120);
        int height = bottom - top;
        int paddingLeft = DisplayUtils.dip2px(10);
        mRectPaint.setColor(item.getColor());
        canvas.drawRoundRect(left+paddingLeft,top,left+rectWidth,bottom,DisplayUtils.dip2px(6),DisplayUtils.dip2px(6),mRectPaint);


        String title = item.getTitle();
        if(TextUtils.isEmpty(title))
            return;
        Rect mRect = new Rect();

        mTextPaint.getTextBounds(title,0,title.length(),mRect);
        int x = left + (rectWidth - mRect.width())/2;
        //int x = left + UIUtils.dip2px(20);
        int y = bottom - (height - mRect.height())/2;
        canvas.drawText(title,x,y,mTextPaint);
    }

    @Override
    protected void onDrawDotItem(Canvas canvas, int cx, int cy, int radius, int pos) {
        ITimeItem item = timeItems.get(pos);
        mDotPaint.setColor(item.getColor());
        canvas.drawCircle(cx,cy,DisplayUtils.dip2px(6),mDotPaint);
    }
}

第二步: 创建数据,实现 ITimeItem 接口

/**
 * Created on 2021/7/16 15:07
 *
 * @author Gong Youqiang
 */
public class TimeItem implements ITimeItem {
    private String name;
    private String title;
    private String detail;
    private int color;
    private int res;

    public TimeItem(String name, String title, String detail, int color, int res) {
        this.name = name;
        this.title = title;
        this.detail = detail;
        this.color = color;
        this.res = res;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public int getRes() {
        return res;
    }

    public void setRes(int res) {
        this.res = res;
    }

    @Override
    public String getTitle() {
        return title;
    }

    @Override
    public int getColor() {
        return color;
    }

    @Override
    public int getResource() {
        return res;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public static List<TimeItem> initStepInfo(){
        List<TimeItem> items = new ArrayList<>();
        items.add(new TimeItem("完善信息", "实践探究", "+30积分", Color.parseColor("#F57F17"), 0));
        items.add(new TimeItem("了解基地", "实践探究", "+30积分", Color.parseColor("#F57F17"), 0));
        items.add(new TimeItem("知识储备", "实践探究", "+30积分", Color.parseColor("#F57F17"), 0));
        items.add(new TimeItem("安全教育主题馆", "实践探究", "+30积分", Color.parseColor("#F57F17"), 0));
        items.add(new TimeItem("评价教师", "总结拓展", "+30积分", Color.parseColor("#0D47A1"), 0));
        items.add(new TimeItem("评价路线", "总结拓展", "+30积分", Color.parseColor("#0D47A1"), 0));
        return items;
    }
}

第三步:创建适配器

/**
 * Created on 2021/7/16 15:25
 *
 * @author Gong Youqiang
 */
public abstract class RecyclerAdapter<Data> extends BaseAdapter<Data> {

    public RecyclerAdapter() {
    }

    public RecyclerAdapter(AdapterListener<Data> adapterListener) {
        super(adapterListener);
    }

    public RecyclerAdapter(List<Data> mDataList, AdapterListener<Data> adapterListener) {
        super(mDataList, adapterListener);
    }

    @Override
    protected void doWithRoot(BaseAdapter.ViewHolder viewHolder, View root) {
        super.doWithRoot(viewHolder, root);

        ((RecyclerAdapter.ViewHolder)viewHolder).unbinder = ButterKnife.bind(viewHolder,root);
    }

    /**
     *  自定义的ViewHolder
     */
    public static abstract class ViewHolder<Data> extends BaseAdapter.ViewHolder<Data>{
        public Unbinder unbinder;

        public ViewHolder(View itemView) {
            super(itemView);
        }
    }

}

第四步:创建时间轴

public class SingleTimeLineActivity extends BaseActivity {
    @BindView(R.id.rv_content)
    RecyclerView mRecyclerView;

    private RecyclerAdapter<TimeItem> mAdapter;


    @Override
    public int getLayoutId() {
        return R.layout.activity_single_time_line;
    }

    @Override
    public void initView() {
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mAdapter = new RecyclerAdapter<TimeItem>() {
            @Override
            public ViewHolder<TimeItem> onCreateViewHolder(View root, int viewType) {
                return new TimeLineViewHolder(root);
            }

            @Override
            public int getItemLayout(TimeItem timeItem, int position) {
                return R.layout.step_recycle_item;
            }
        });

        List<TimeItem> timeItems = TimeItem.initStepInfo();
        mAdapter.addAllData(timeItems);
        TimeLine decoration = new SingleTimeLineDecoration.Builder(this, timeItems)
                .setTitle(Color.parseColor("#ffffff"), 20)
                .setTitleStyle(SingleTimeLineDecoration.FLAG_TITLE_TYPE_TOP, 40)
                .setLine(SingleTimeLineDecoration.FLAG_LINE_DIVIDE, 50, Color.parseColor("#8d9ca9"))
                .setDot(SingleTimeLineDecoration.FLAG_DOT_DRAW)
                .setSameTitleHide()
                .build(StepSTL.class);
        mRecyclerView.addItemDecoration(decoration);

        
    }

    class TimeLineViewHolder extends RecyclerAdapter.ViewHolder<TimeItem> {

        @BindView(R.id.tv_title)
        TextView mTitleTv;
        @BindView(R.id.tv_content)
        TextView mContentTv;

        public TimeLineViewHolder(View itemView) {
            super(itemView);
        }

        @Override
        protected void onBind(TimeItem timeItem) {
            mTitleTv.setText(timeItem.getName());
            mContentTv.setText(timeItem.getDetail());
        }
    }
}

第五步 工具类

package com.hk.launcherdemo.utils;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.GradientDrawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.ColorInt;
import androidx.annotation.LayoutRes;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * Created on 2021/6/30 16:35
 *
 * @author Gong Youqiang
 */
public class DisplayUtils {
    public static int dpToPx(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
    }

    public static int pxToDp(float px) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, px, Resources.getSystem().getDisplayMetrics());
    }

    public static int getViewMeasuredHeight(TextView tv) {
        tv.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        return tv.getMeasuredHeight();

    }

    /**
     * dp转px
     */
    public static int dip2px(Context context, float dpValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * dp转px
     */
    public static int dip2px(float dpValue) {
        float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 将px值转换为sp值,保证文字大小不变
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * 将sp值转换为px值,保证文字大小不变
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    public static boolean hasEmpty(List<TextView> edits) {
        for (TextView editText : edits) {
            if (TextUtils.isEmpty(editText.getText().toString().trim())) {
                return true;
            }
        }
        return false;
    }

    public static boolean hasEmpty(TextView... edits) {
        for (TextView editText : edits) {
            if (TextUtils.isEmpty(editText.getText().toString().trim())) {
                return true;
            }
        }
        return false;
    }

    public static boolean hasEmpty(ImageView[] edits) {
        for (ImageView imageView : edits) {
            if (TextUtils.isEmpty(imageView.getTag().toString().trim())) {
                return true;
            }
        }
        return false;
    }



    public static View inflaterLayout(Context context, @LayoutRes int layoutRes) {
        LayoutInflater inflater = LayoutInflater.from(context);
        return inflater.inflate(layoutRes, null);
    }

    /**
     * 圆角Drawable
     *
     * @param radius 圆角
     * @param color  填充颜色
     */
    public static GradientDrawable getShapeDrawable(int radius, @ColorInt int color) {
        GradientDrawable gd = new GradientDrawable();
        gd.setColor(color);
        gd.setCornerRadius(radius);
        return gd;
    }

    private static SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * 日期转yyyy-MM-dd格式
     * @param date 日期
     * @return String
     */
    public static String date2SDayFormat(Date date){
        return dayFormat.format(date);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin-Dev

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值