自定义View实现时间轴

时间轴

时间轴,顾名思义就是将发生的事件按照时间顺序罗列起来,给用户带来一种更加直观的体验。京东和淘宝的物流顺序就是一个时间轴(如图),想必大家都不陌生。

 

时间轴的初探

初次见到这种UI,感觉整个布局似曾相识,但面对那条时间轴却又不知如何下手。其实,整个时间轴还是可以当做一个ListView处理,只是在Adapter中的布局要花点心思。好了,废话不说,看代码。

Attrs 属性

开始控件之前先准备好需要的属性。

<resources>
    <declare-styleable name="TimeLineMarker">
        <attr name="markerSize" format="dimension" />
        <attr name="marker" format="color|reference" />
        <attr name="beginLine" format="color|reference" />
        <attr name="endLine" format="color|reference" />
        <attr name="lineSize" format="dimension" />
    </declare-styleable>
</resources>

控件很简单,首先我们继承View,取名为 TimeLineMarker 就OK。

public class TimeLineMarker extends View {
    private int mMarkerSize = 24;
    private int mLineSize = 12;
    private Drawable mBeginLine;
    private Drawable mEndLine;
    private Drawable mMarkerDrawable;

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBeginLine != null) {
            mBeginLine.draw(canvas);
        }

        if (mEndLine != null) {
            mEndLine.draw(canvas);
        }

        if (mMarkerDrawable != null) {
            mMarkerDrawable.draw(canvas);
        }

        super.onDraw(canvas);
    }

    public TimeLineMarker(Context context) {
        this(context, null);
    }

    public TimeLineMarker(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TimeLineMarker(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        // Load attributes
        final TypedArray a = getContext().obtainStyledAttributes(
                attrs, R.styleable.TimeLineMarker);

        mMarkerSize = a.getDimensionPixelSize(
                R.styleable.TimeLineMarker_markerSize,
                mMarkerSize);

        mLineSize = a.getDimensionPixelSize(
                R.styleable.TimeLineMarker_lineSize,
                mLineSize);

        mBeginLine = a.getDrawable(
                R.styleable.TimeLineMarker_beginLine);

        mEndLine = a.getDrawable(
                R.styleable.TimeLineMarker_endLine);

        mMarkerDrawable = a.getDrawable(
                R.styleable.TimeLineMarker_marker);

        a.recycle();

        if (mBeginLine != null)
            mBeginLine.setCallback(this);

        if (mEndLine != null)
            mEndLine.setCallback(this);

        if (mMarkerDrawable != null)
            mMarkerDrawable.setCallback(this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec,heightMeasureSpec);

        int w = getPaddingLeft() + getPaddingRight();
        int h = getPaddingTop() + getPaddingBottom();

        if (mMarkerDrawable != null) {
            w += mMarkerSize;
            h += mMarkerSize;
        }

        w = Math.max(w, getMeasuredWidth());
        h = Math.max(h, getMeasuredHeight());

        int widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
        int heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);


        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        initDrawableSize();
    }

    private void initDrawableSize() {
        int pLeft = getPaddingLeft();
        int pRight = getPaddingRight();
        int pTop = getPaddingTop();
        int pBottom = getPaddingBottom();

        int width = getWidth();
        int height = getHeight();

        int cWidth = width - pLeft - pRight;
        int cHeight = height - pTop - pBottom;

        Rect bounds;

        if (mMarkerDrawable != null) {
            // Size
            int markerSize = Math.min(mMarkerSize, Math.min(cWidth, cHeight));
            mMarkerDrawable.setBounds(pLeft, pTop,
                    pLeft + markerSize, pTop + markerSize);

            bounds = mMarkerDrawable.getBounds();
        } else {
            bounds = new Rect(pLeft, pTop, pLeft + cWidth, pTop + cHeight);
        }


        int halfLineSize = mLineSize >> 1;
        int lineLeft = bounds.centerX() - halfLineSize;

        if (mBeginLine != null) {
            mBeginLine.setBounds(lineLeft, 0, lineLeft + mLineSize, bounds.top);
        }

        if (mEndLine != null) {
            mEndLine.setBounds(lineLeft, bounds.bottom, lineLeft + mLineSize, height);
        }
    }


    public void setLineSize(int lineSize) {
        if (mLineSize != lineSize) {
            this.mLineSize = lineSize;
            initDrawableSize();
            invalidate();
        }
    }

    public void setMarkerSize(int markerSize) {
        if (this.mMarkerSize != markerSize) {
            mMarkerSize = markerSize;
            initDrawableSize();
            invalidate();
        }
    }

    public void setBeginLine(Drawable beginLine) {
        if (this.mBeginLine != beginLine) {
            this.mBeginLine = beginLine;
            if (mBeginLine != null) {
                mBeginLine.setCallback(this);
            }
            initDrawableSize();
            invalidate();
        }
    }

    public void setEndLine(Drawable endLine) {
        if (this.mEndLine != endLine) {
            this.mEndLine = endLine;
            if (mEndLine != null) {
                mEndLine.setCallback(this);
            }
            initDrawableSize();
            invalidate();
        }
    }

    public void setMarkerDrawable(Drawable markerDrawable) {
        if (this.mMarkerDrawable != markerDrawable) {
            this.mMarkerDrawable = markerDrawable;
            if (mMarkerDrawable != null) {
                mMarkerDrawable.setCallback(this);
            }
            initDrawableSize();
            invalidate();
        }
    }
}

使用

XML布局

ITEM布局item_time_line.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    xmlns:app="http://schemas.android.com/apk/res-auto">
<com.example.v_xinxxliu.recycleview_video_demo.TimeLineMarker
    android:id="@+id/item_time_line_mark"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:paddingBottom="16dp"
    android:paddingLeft="4dp"
    android:paddingRight="4dp"
    android:paddingTop="16dp"
    app:lineSize="2dp"
    app:beginLine="@color/colorAccent"
    app:endLine="@color/colorAccent"
    app:marker="@drawable/pause"
    app:markerSize="24dp"/>
    <TextView
        android:id="@+id/item_time_line_txt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:paddingBottom="16dp"
        android:paddingLeft="4dp"
        android:paddingRight="4dp"
        android:paddingTop="16dp"
        android:textColor="@color/colorPrimary"
        android:textSize="16dp" />
</LinearLayout>

在这里我们之间使用顺序布局,左边是TimelIne控件,右边是一个简单的字体控件,具体使用中可以细化一些。 
在TImeLine控件中我们的Mark是使用的drawable/ic_timeline_default_marker;这个就是一个简单的圆圈而已;对于自己美化可以使用一张图片代替或者更加复杂的布局;当然上面的线条就更加简单了,就直接使用颜色代替。

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/colorAccent" />
    <stroke
        android:width="1dp"
        android:color="@color/colorPrimary" />
</shape>

主界面XML RecyclerView

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/time_line_recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

数据类

public class TimeLineModel {
    private String name;
    private int age;

    public TimeLineModel() {

    }

    public TimeLineModel(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

ItemType.java

public class ItemType {
    public final static int NORMAL = 0;
 
    public final static int HEADER = 1;
    public final static int FOOTER = 2;
 
    public final static int START = 4;
    public final static int END = 8;
 
    public final static int ATOM = 16;
}

TimeLineViewHolder.java

public class TimeLineViewHolder extends RecyclerView.ViewHolder {
    private TextView mName;
 
    public TimeLineViewHolder(View itemView, int type) {
        super(itemView);
 
        mName = (TextView) itemView.findViewById(R.id.item_time_line_txt);
 
        TimeLineMarker mMarker = (TimeLineMarker) itemView.findViewById(R.id.item_time_line_mark);
        if (type == ItemType.ATOM) {
            mMarker.setBeginLine(null);
            mMarker.setEndLine(null);
        } else if (type == ItemType.START) {
            mMarker.setBeginLine(null);
        } else if (type == ItemType.END) {
            mMarker.setEndLine(null);
        }
 
    }
 
    public void setData(TimeLineModel data) {
        mName.setText("Name:" + data.getName() + " Age:" + data.getAge());
    }
}

该文件为RecyclerView 的Adapter中每个Item需要实现的Holder类。 
在该类中,我们在构造函数中需要传入一个根View同时传入一个当然item的状态。 
随后使用find….找到控件,在这里我们把TextView保存起来,而TimeLineView找到后直接进行初始化设置。 
根据传入的ItemType来判断是否是第一个,最后一个,以及原子;然后设置TimeLineView的属性。 
在下面的setData方法中我们显示具体的Model数据。

TimeLineAdapter.java

适配器部分,我们需要做的工作是;根据具体的数据渲染上对应的界面就OK。

public class TimeLineAdapter extends RecyclerView.Adapter<TimeLineViewHolder> {
    private List<TimeLineModel> mDataSet;

    public TimeLineAdapter(List<TimeLineModel> models) {
        mDataSet = models;
    }

    @Override
    public int getItemViewType(int position) {
        final int size = mDataSet.size() - 1;
        if (size == 0)
            return ItemType.ATOM;
        else if (position == 0)
            return ItemType.START;
        else if (position == size)
            return ItemType.END;
        else return ItemType.NORMAL;
    }

    @Override
    public TimeLineViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        // Create a new view.
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.item_time_line, viewGroup, false);
        return new TimeLineViewHolder(v, viewType);
    }

    @Override
    public void onBindViewHolder(TimeLineViewHolder timeLineViewHolder, int i) {
        timeLineViewHolder.setData(mDataSet.get(i));
    }

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

在这里需要着重说一下:我复写了getItemViewType方法;在该方法中我们需要设置对应的Item的类型;在这里传入的是item的坐标,需要返回的是item的具体状态,该状态标示是int类型;在这里我使用的是ItemType的静态属性。

该方法会在调用onCreateViewHolder方法之前调用;而onCreateViewHolder方法中的第二个参数int值也就是从getItemViewType之中来;所以我们可以在这里进行对应的数据状态标示。

而在onCreateViewHolder方法中我们返回一个:TimeLineViewHolder就OK,随后在onBindViewHolder方法中进行数据初始化操作。

MainActivity.java

上面所有都准备好了,下面就进行具体的显示。 
在这里就只贴出核心代码了;篇幅也是有些长。

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecycler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecycler = (RecyclerView) findViewById(R.id.time_line_recycler);
        initRecycler();
    }

    private void initRecycler() {
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);

        TimeLineAdapter adapter = new TimeLineAdapter(getData());

        mRecycler.setLayoutManager(layoutManager);
        mRecycler.setAdapter(adapter);
    }

    private List<TimeLineModel> getData() {
        List<TimeLineModel> models = new ArrayList<TimeLineModel>();

        models.add(new TimeLineModel("XiaoMing", 21));
        models.add(new TimeLineModel("XiaoFang", 20));
        models.add(new TimeLineModel("XiaoHua", 25));
        models.add(new TimeLineModel("XiaoA", 22));
        models.add(new TimeLineModel("XiaoNiu", 23));

        return models;
    }
}

大功告成!!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值