Android

Kevin Learn Android:双列时间轴

2021-07-19  本文已影响0人  Kevin_小飞象
每日一图.jpg

效果图

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;
        }

    }
}

DoubleTimeLineDecoration.java

/**
 * Created on 2021/7/19 9:04
 *
 * @author Gong Youqiang
 */
public abstract class DoubleTimeLineDecoration extends TimeLine {
    public static final int LEFT = 0;
    public static final int RIGHT = 1;

    private int mStartSide;

    public DoubleTimeLineDecoration(Config config) {
        super(config);
        mStartSide = LEFT;
    }

    /**
     * 设置起始边
     * @param startSide Left RIGHT
     */
    public void setStartSide(int startSide) {
        this.mStartSide = startSide;
    }

    @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);

        int side = pos % 2;
        if ((mFlag & FLAG_TITLE_TYPE_LEFT) != 0) {
            if (side == mStartSide)
                outRect.set(0, 0, (mLineOffset+mLineWidth)/2 + mLeftOffset, 0);
            else
                outRect.set((mLineOffset+mLineWidth)/2 + mLeftOffset, 0, 0, 0);
        } else {
            if (side == mStartSide)
                outRect.set(0, 0, (mLineOffset+mLineWidth)/2, 0);
            else
                outRect.set((mLineOffset+mLineWidth)/2, 0, 0, 0);
        }
    }


    @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);
    }

    /**
     * 绘制标题
     */
    @Override
    protected void drawTitle(Canvas canvas, RecyclerView parent) {
        int childCount = parent.getChildCount();
        // 注意:隐藏相同标题对两侧布局不重要
        int centerX = parent.getMeasuredWidth() / 2;
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            int top = child.getTop();
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int pos = params.getViewAdapterPosition();
            ITimeItem timeItem = timeItems.get(pos);
            int mLeft, mTop, mRight, mBottom;

            if ((mFlag & FLAG_TITLE_TYPE_LEFT) != 0) {
                if (child.getLeft() >= parent.getMeasuredWidth() / 2) {
                    mLeft = parent.getPaddingLeft();
                    mTop = child.getTop();
                    mRight = child.getLeft() - params.leftMargin;
                    mBottom = child.getBottom();
                    if ((mFlag & FLAG_TITLE_DRAW_BG) != 0)
                        canvas.drawRect(mLeft, mTop, mRight, mBottom, mBgPaint);
                    onDrawTitleItem(canvas, mLeft, mTop, mRight, mBottom, centerX, pos, false);
                } else {
                    mLeft = child.getRight() + params.rightMargin;
                    mTop = child.getTop();
                    mRight = parent.getMeasuredWidth() - parent.getPaddingRight();
                    mBottom = child.getBottom();
                    if ((mFlag & FLAG_TITLE_DRAW_BG) != 0)
                        canvas.drawRect(mLeft, mTop, mRight, mBottom, mBgPaint);
                    onDrawTitleItem(canvas, mLeft, mTop, mRight, mBottom, centerX, pos, true);
                }
            }
        }
    }

    /**
     * 绘制标题
     *
     * @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 centerX, int pos, boolean isLeft);


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

        if ((mFlag & FLAG_LINE_CONSISTENT) != 0) {
            View lastChild = parent.getChildAt(childCount - 1);
            bottom = lastChild.getBottom();
            c.drawLine(parent.getMeasuredWidth() / 2, top, parent.getMeasuredWidth() / 2, bottom, mLinePaint);
        } else {
            View firstChild = parent.getChildAt(0);
            top = (firstChild.getTop() + firstChild.getBottom()) / 2;
            View lastChild = parent.getChildAt(childCount - 1);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) lastChild.getLayoutParams();
            if(params.getViewAdapterPosition() == timeItems.size() - 1) {
                bottom = (lastChild.getBottom() + lastChild.getTop()) / 2;
            }else {
                bottom = lastChild.getBottom();
            }
            c.drawLine(parent.getMeasuredWidth() / 2, top, parent.getMeasuredWidth() / 2, bottom, 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);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
            int pos = params.getViewAdapterPosition();
            // 圆心坐标
            int cx, cy;
            int top = child.getTop();
            int bottom = child.getBottom();
            cx = parent.getMeasuredWidth() / 2;
            cy = (bottom + top) / 2;
            int r = (top - bottom) / 2;
            r = Math.min((mLineOffset + mLineWidth) / 2, r);

            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){

    }
}

实例

第一步: 继承 DoubleTimeLineDecoration

/**
 * Created on 2021/7/19 9:10
 *
 * @author Gong Youqiang
 */
public class DateInfoDTL extends DoubleTimeLineDecoration {
    private static final String[] MONTHS = new String[]{
            "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
    };

    private int r;
    private Paint monTextPaint;
    private Paint dayTextPaint;
    private int space;

    public DateInfoDTL(Config config) {
        super(config);

        r = DisplayUtils.dip2px(24);
        monTextPaint = new Paint();
        monTextPaint.setTextSize(DisplayUtils.sp2px(mContext,12));
        monTextPaint.setColor(Color.parseColor("#F5F5F5"));

        dayTextPaint = new Paint();
        dayTextPaint.setTextSize(DisplayUtils.sp2px(mContext,18));
        dayTextPaint.setColor(Color.parseColor("#ffffff"));

        space = DisplayUtils.dip2px(6);

        mDotPaint.setMaskFilter(new BlurMaskFilter(6, BlurMaskFilter.Blur.SOLID));

    }

    @Override
    protected void onDrawTitleItem(Canvas canvas, int left, int top, int right, int bottom, int centerX, int pos, boolean isLeft) {
        // 不需要做什么
    }

    @Override
    protected void onDrawDotItem(Canvas canvas, int cx, int cy, int radius, int pos) {
        super.onDrawDotItem(canvas, cx, cy, radius, pos);

        DateInfo timeItem = (DateInfo) timeItems.get(pos);
        Date date = timeItem.getDate();
        mDotPaint.setColor(timeItem.getColor());
        canvas.drawCircle(cx,cy,r,mDotPaint);

        if(date != null){
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            String mon = MONTHS[calendar.get(Calendar.MONTH)];
            int n = calendar.get(Calendar.DAY_OF_MONTH);
            String day = n<10?"0"+n:Integer.toString(n);

            Rect monRect = new Rect();
            monTextPaint.getTextBounds(mon,0,mon.length(),monRect);
            Rect dayRect = new Rect();
            dayTextPaint.getTextBounds(day,0,day.length(),dayRect);

            int monWidth = monRect.width();
            int monHeight = monRect.height();
            int dayWidth = dayRect.width();
            int dayHeight = dayRect.height();

            int beginY = cy + r - (r * 2 - monHeight - dayHeight)/2;
            canvas.drawText(day,cx-dayWidth/2,beginY,dayTextPaint);
            beginY -= dayHeight + space;
            canvas.drawText(mon,cx-monWidth/2,beginY,monTextPaint);
        }
    }
}

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

/**
 * Created on 2021/7/16 15:56
 *
 * @author Gong Youqiang
 */
public class DateInfo implements ITimeItem {

    private String name;
    private String detail;
    private Date date;
    private int color;

    public DateInfo(String name, String detail, Date date, int color) {
        this.name = name;
        this.detail = detail;
        this.date = date;
        this.color = color;
    }

    public String getName() {
        return name;
    }

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

    public String getDetail() {
        return detail;
    }

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


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

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

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

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

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

    public static List<DateInfo> initDateInfo() {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        List<DateInfo> items = new ArrayList<>();
        items.add(new DateInfo("喝茶", "第一天养养生吧~", calendar.getTime(), Color.parseColor("#f36c60")));
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        items.add(new DateInfo("喝酒", "今天找老徐吃烧烤", calendar.getTime(), Color.parseColor("#ab47bc")));
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        items.add(new DateInfo("画画", "去鼋头渚写生", calendar.getTime(), Color.parseColor("#aed581")));
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        items.add(new DateInfo("高尔夫", "约个高尔夫", calendar.getTime(), Color.parseColor("#5FB29F")));
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        items.add(new DateInfo("游泳", "今天来洗个澡", calendar.getTime(), Color.parseColor("#ec407a")));
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        items.add(new DateInfo("温泉", "快上班了好好休息", calendar.getTime(), Color.parseColor("#ffd54f")));
        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 DoubelActivity extends BaseActivity {
    @BindView(R.id.rv_content)
    RecyclerView mRecyclerView;

    private RecyclerAdapter<DateInfo> mAdapter;
    @Override
    public int getLayoutId() {
        return R.layout.activity_doubel;
    }

    @Override
    public void initView() {
        mRecyclerView.setLayoutManager(new DoubleSideLayoutManager(DoubleSideLayoutManager.START_LEFT, DisplayUtils.dip2px(40)));
        mRecyclerView.setAdapter(mAdapter = new RecyclerAdapter<DateInfo>() {
            @Override
            public ViewHolder<DateInfo> onCreateViewHolder(View root, int viewType) {
                return new DateInfoHolder(root);
            }

            @Override
            public int getItemLayout(DateInfo s, int position) {
                return R.layout.date_info_recycle_item;
            }
        });

        List<DateInfo> timeItems = DateInfo.initDateInfo();
        mAdapter.addAllData(timeItems);

        TimeLine timeLine = provideTimeLine(timeItems);
        mRecyclerView.addItemDecoration(timeLine);
    }

    private TimeLine provideTimeLine(List<DateInfo> timeItems) {
        return new TimeLine.Builder(this, timeItems)
                .setTitleStyle(TimeLine.FLAG_TITLE_POS_NONE, 0)
                .setLine(TimeLine.FLAG_LINE_BEGIN_TO_END, 60, Color.parseColor("#757575"), 2)
                .setDot(TimeLine.FLAG_DOT_DRAW)
                .build(DateInfoDTL.class);
    }

    class DateInfoHolder extends RecyclerAdapter.ViewHolder<DateInfo> {

        @BindView(R.id.tv_name)
        TextView mNameTv;

        @BindView(R.id.tv_detail)
        TextView mDetailTv;

        DateInfoHolder(View itemView) {
            super(itemView);
        }

        @Override
        protected void onBind(DateInfo timeItem) {
            mNameTv.setText(timeItem.getName());
            mDetailTv.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);
    }
}

实例二

  1. 效果图


    02.jpg

第一步: 继承 DoubleTimeLineDecoration

/**
 * Created on 2021/7/19 9:48
 *
 * @author Gong Youqiang
 */
public class WeekPlanDTL extends DoubleTimeLineDecoration {
    public WeekPlanDTL(Config config) {
        super(config);
    }

    @Override
    protected void onDrawTitleItem(Canvas canvas, int left, int top, int right, int bottom, int centerX, int pos, boolean isLeft) {
        // Draw title part
        ITimeItem item = timeItems.get(pos);

        int height = bottom - top;
        String title = item.getTitle();
        if (TextUtils.isEmpty(title))
            return;
        Rect mRect = new Rect();
        //mTextPaint.setColor(item.getColor());
        mTextPaint.getTextBounds(title, 0, title.length(), mRect);

        int x, y;
        if (isLeft) {
            x = centerX + DisplayUtils.dip2px(30);
        } else {
            x = centerX - DisplayUtils.dip2px(30) - mRect.width();
        }
        y = bottom - (height - mRect.height()) / 2;
        canvas.drawText(title, x, y, mTextPaint);
    }

    @Override
    protected void onDrawDotResItem(Canvas canvas, int cx, int cy, int radius, Drawable drawable, int pos) {
        super.onDrawDotResItem(canvas, cx, cy, radius, drawable, pos);
        // draw dot part
        if (drawable != null) {
            int height = drawable.getIntrinsicHeight();
            int width = drawable.getIntrinsicWidth();
            int left = cx - width / 2;
            int top = cy - height / 2;
            int right = cx + width / 2;
            int bottom = cy + height / 2;
            drawable.setBounds(left, top, right, bottom);
            drawable.draw(canvas);
            mDotPaint.setStyle(Paint.Style.STROKE);
            mDotPaint.setColor(Color.parseColor("#ffffff"));
            mDotPaint.setStrokeWidth(DisplayUtils.dip2px(2));
            canvas.drawCircle(cx, cy, width / 2 - DisplayUtils.dip2px(3), 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;
    }

    public static List<TimeItem> initTimeInfo(){
        List<TimeItem> items = new ArrayList<>();
        items.add(new TimeItem("喝茶", "10-01,周二", "第一天养养生吧~", Color.parseColor("#f36c60"), R.drawable.timeline_ic_tea));
        items.add(new TimeItem("喝酒", "06-12,周三", "今天找老徐吃烧烤", Color.parseColor("#ab47bc"), R.drawable.timeline_ic_drink));
        items.add(new TimeItem("画画", "07-07,周四", "去鼋头渚写生", Color.parseColor("#aed581"), R.drawable.timeline_ic_draw));
        items.add(new TimeItem("高尔夫", "08-20,周五", "约个高尔夫", Color.parseColor("#5FB29F"), R.drawable.timeline_ic_golf));
        items.add(new TimeItem("游泳", "09-16,周六", "今天来洗个澡", Color.parseColor("#ec407a"), R.drawable.timeline_ic_bath));
        items.add(new TimeItem("温泉", "10-01,周日", "快上班了好好休息", Color.parseColor("#ffd54f"), R.drawable.timeline_ic_footer));
        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 DoubelActivity extends BaseActivity {
    @BindView(R.id.rv_content)
    RecyclerView mRecyclerView;

    private RecyclerAdapter<TimeItem> mAdapter;
    @Override
    public int getLayoutId() {
        return R.layout.activity_doubel;
    }

    @Override
    public void initView() {
        mRecyclerView.setLayoutManager(new DoubleSideLayoutManager(DoubleSideLayoutManager.START_LEFT, DisplayUtils.dip2px(40)));
        mRecyclerView.setAdapter(mAdapter = new RecyclerAdapter<TimeItem>() {
            @Override
            public ViewHolder<TimeItem> onCreateViewHolder(View root, int viewType) {
                return new WeekPlanViewHolder(root);
            }

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

        List<TimeItem> timeItems = TimeItem.initTimeInfo();
        mAdapter.addAllData(timeItems);

        TimeLine timeLine = provideTimeLine(timeItems);
        mRecyclerView.addItemDecoration(timeLine);
    }

    private TimeLine provideTimeLine(List<TimeItem> timeItems) {
        return new TimeLine.Builder(this, timeItems)
                .setTitle(Color.parseColor("#8d9ca9"), 14)
                .setTitleStyle(TimeLine.FLAG_TITLE_TYPE_LEFT, 0)
                .setLine(TimeLine.FLAG_LINE_BEGIN_TO_END, 60, Color.parseColor("#757575"),3)
                .setDot(TimeLine.FLAG_DOT_RES)
                .build(WeekPlanDTL.class);
    }

    class WeekPlanViewHolder extends RecyclerAdapter.ViewHolder<TimeItem> {

        @BindView(R.id.tv_name)
        TextView mNameTv;

        @BindView(R.id.tv_detail)
        TextView mDetailTv;

        @BindView(R.id.btn_go)
        TextView mGoBtn;

        @BindView(R.id.btn_write)
        TextView mWriteBtn;

        WeekPlanViewHolder(View itemView) {
            super(itemView);
        }

        @Override
        protected void onBind(TimeItem timeItem) {
            mNameTv.setText(timeItem.getName());
            mDetailTv.setText(timeItem.getDetail());

            setColor(timeItem.getColor());
        }

        private void setColor(int color){
            mGoBtn.setBackgroundColor(color);
            mWriteBtn.setBackgroundColor(color);
        }
    }

}
上一篇下一篇

猜你喜欢

热点阅读