本文实例为大家分享了Android时光轴的制作方法,供大家参考,具体内容如下
1. 效果
2.分析和实现
2.1效果实现:
之前想了一下这种效果,因为只需要用到自己的项目中所以采用图片直接布局的形式去实现效果,虽然效果实现了,但是后来发现了出了很多问题:第一Android的分辨率太多了直接设置xxxdp难免有部分机型出现不适配的情况,第二我们与右边这部分需要对齐的问题这个就比较头大。
所以目前的实现效果方式是这样子的:
1.自定义TimerLineMarker,根据自定义属性获取点和线的背景资源或是颜色以及宽度等等,在onMeasure中计算布局的宽度和高度;
2.在Item布居中我们给需要对齐那个View设置一个id为need_align_view,我们在onSizeChanged中去找到并计算对齐View距头部的高度;
3.当我们得到对齐View的高度后,我们计算上面Line,中间Marker以及下面Line需要绘制的矩形区域,调用invalidate()然后在onDraw方法中分别绘制这三个部分;
4.很显然我们需要显示的方式是有些不同的,比如第一个没有上面的线其中心标记颜色也不一样,最后一个没有下面的线,所以我们需要提供两个方法:setStyle()设置显示风格;setMarker(int resouceId)设置中间标记的资源
2.2分步实现:
1.自定义TimerLineMarker,根据自定义属性获取点和线的背景资源或是颜色以及宽度等等,在onMeasure中计算布局的宽度和高度
public class TimerLineMarker extends View { // 3个部分的drawable private Drawable mBeginLine, mEndLine, mMarker; // 显示大小 private int mMarkerSize = 26, mLineSize = 4; // 距离头部的微调 private int mMarkerMarginTop = 0; public TimerLineMarker(Context context) { this(context, null); } public TimerLineMarker(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TimerLineMarker(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttribute(attrs); } /** * 初始化自定义属性 */ private void initAttribute(AttributeSet attrs) { final TypedArray typedArray = getContext().obtainStyledAttributes( attrs, R.styleable.TimerLineMarker); // 获取size mMarkerSize = typedArray.getDimensionPixelSize( R.styleable.TimerLineMarker_markerSize, mMarkerSize); mLineSize = typedArray.getDimensionPixelSize( R.styleable.TimerLineMarker_lineSize, mLineSize); // 获取drawable mBeginLine = typedArray .getDrawable(R.styleable.TimerLineMarker_beginLine); mEndLine = typedArray.getDrawable(R.styleable.TimerLineMarker_endLine); mMarker = typedArray.getDrawable(R.styleable.TimerLineMarker_marker); mMarkerMarginTop = typedArray.getDimensionPixelSize( R.styleable.TimerLineMarker_markerMarginTop, mMarkerMarginTop); typedArray.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 测量本View的宽高里面子控件的宽度 int with = mMarkerSize + getPaddingLeft() + getPaddingRight(); int height = mMarkerSize + getPaddingTop() + getPaddingBottom(); // 通过系统的一个方法做决策最终决定宽高 int withSize = resolveSizeAndState(with, widthMeasureSpec, 0); int heightSize = resolveSizeAndState(height, heightMeasureSpec, 0); // 设置宽高 setMeasuredDimension(withSize, heightSize); } }
2.在Item布居中我们给需要对齐那个View设置一个id为need_align_view,我们在onSizeChanged中去找到并计算对齐View距头部的高度;
// 标记距离头部的位置 private int mMarkerTopDistance; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); initAlignViewHeight(); // 当View显示的时候回调 // 定位到当前几个draw able的坐标,然后绘制 initDrawable(); } /** * 初始化获取需要对齐的View高度 */ private void initAlignViewHeight() { // 获取需要对齐的View ViewGroup parent = (ViewGroup) this.getParent(); mNeedAlignView = findNeedAlignView(parent); // 获取需要对齐的View距离顶部的高度 if (mNeedAlignView != null) { mMarkerTopDistance = 0; // 与需要对齐View的中心点对齐 mMarkerTopDistance += calcViewTop(mNeedAlignView) + mNeedAlignView.getMeasuredHeight() / 2; } } /** * 循环获取距顶部的距离 */ private int calcViewTop(View view) { final ViewGroup parentView = (ViewGroup) view.getParent(); final int childCount = parentView.getChildCount(); // 先加上paddingTop int topDistance = parentView.getPaddingTop(); for (int i = 0; i < childCount; i++) { final View childView = parentView.getChildAt(i); final ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) childView .getLayoutParams(); topDistance = addTopMargin(topDistance, params); if (childView == view) { return topDistance; } topDistance = addBottomMargin(topDistance, params); topDistance += childView.getMeasuredHeight(); } return topDistance; } /** * 累加底部的margin高度 */ private int addBottomMargin(int topDistance, ViewGroup.LayoutParams params) { if (params instanceof RelativeLayout.LayoutParams) { RelativeLayout.LayoutParams param = (RelativeLayout.LayoutParams) params; topDistance += param.bottomMargin; } if (params instanceof LinearLayout.LayoutParams) { LinearLayout.LayoutParams param = (LinearLayout.LayoutParams) params; topDistance += param.bottomMargin; } if (params instanceof FrameLayout.LayoutParams) { FrameLayout.LayoutParams param = (FrameLayout.LayoutParams) params; topDistance += param.bottomMargin; } if (params instanceof TableLayout.LayoutParams) { TableLayout.LayoutParams param = (TableLayout.LayoutParams) params; topDistance += param.bottomMargin; } return topDistance; } /** * 累加头部margin高度 */ private int addTopMargin(int topDistance, ViewGroup.LayoutParams params) { if (params instanceof RelativeLayout.LayoutParams) { RelativeLayout.LayoutParams param = (RelativeLayout.LayoutParams) params; topDistance += param.topMargin; } if (params instanceof LinearLayout.LayoutParams) { LinearLayout.LayoutParams param = (LinearLayout.LayoutParams) params; topDistance += param.topMargin; } if (params instanceof FrameLayout.LayoutParams) { FrameLayout.LayoutParams param = (FrameLayout.LayoutParams) params; topDistance += param.topMargin; } if (params instanceof TableLayout.LayoutParams) { TableLayout.LayoutParams param = (TableLayout.LayoutParams) params; topDistance += param.topMargin; } return topDistance; }
3.当我们得到对齐View的高度后,我们计算上面Line,中间Marker以及下面Line需要绘制的矩形区域,调用invalidate()然后在onDraw方法中分别绘制这三个部分;
/** * 初始化Draw able */ private void initDrawable() { initMarkerBounds(); initLineBounds(); postInvalidate(); } /** * 初始化时光线Bounds */ private void initLineBounds() { int height = getHeight(); Rect bounds = mMarker.getBounds(); int lineLeft = bounds.centerX() - (mLineSize >> 1); if (mBeginLine != null) mBeginLine.setBounds(lineLeft, 0, lineLeft + mLineSize, bounds.top); if (mEndLine != null) mEndLine.setBounds(lineLeft, bounds.bottom, lineLeft + mLineSize, height); } /** * 初始化标记Bounds */ private void initMarkerBounds() { int pLeft = getPaddingLeft(); int pRight = getPaddingRight(); int pBottom = getPaddingBottom(); int pTop = getPaddingTop(); int width = getWidth(); int height = getHeight(); int cWidth = width - pLeft - pRight; int cHeight = height - pTop - pBottom; mMarkerSize = Math.min(mMarkerSize, Math.min(cWidth, cHeight)); mMarkerTopDistance = mMarkerTopDistance - mMarkerSize / 2; if (mMarkerMarginTop < 0) { mMarkerMarginTop = 0; } mMarker.setBounds(pLeft, mMarkerTopDistance + mMarkerMarginTop, pLeft + mMarkerSize, mMarkerTopDistance + mMarkerMarginTop + mMarkerSize); } @Override protected void onDraw(Canvas canvas) { if (mMarker.getBounds().right <= 0) { // 如果bounds被弄丢了 assignValue(); } if (mMarkerStyle != MarkerStyle.START_STYLE) { if (mBeginLine != null) mBeginLine.draw(canvas); } mMarker.draw(canvas); if (mMarkerStyle != MarkerStyle.END_STYLE) { if (mEndLine != null) mEndLine.draw(canvas); } } /** * 从新赋值 */ private void assignValue() { initAlignViewHeight(); initMarkerBounds(); initLineBounds(); }
4.很显然我们需要显示的方式是有些不同的,比如第一个没有上面的线其中心标记颜色也不一样,最后一个没有下面的线,所以我们需要提供两个方法:setStyle()设置显示风格;setMarker(int resouceId)设置中间标记的资源。
/** * 设置显示的分隔 */ public void setStyle(MarkerStyle markerStyle) { this.mMarkerStyle = markerStyle; invalidate(); } /** * 设置标记的Draw able */ public void setMarker(Drawable marker) { this.mMarker = marker; postInvalidate(); } /** * 设置标记资源 * * @param resouceId * 资源id */ public void setMarker(int resouceId) { this.mMarker = getResources().getDrawable(resouceId); postInvalidate(); } /** * 时光轴显示风格 */ public enum MarkerStyle { // 开始第一个 START_STYLE, // 中间位置 CENTER_STYLE, // 最后一个 END_STYLE }
以后希望自己有点空,就把自己做的一些东西写下来. 一方面锻炼一下自己的写文档的能力,另一方面分享代码的同时也希望能与大家交流一下技术,共同学习,共同进步。因为开发过程中遇到一些问题我总会先在网上找一些例子参考一下,类似的代码,可能有些达不到效果或是用不上,没办法也只能自己造轮子。
源码下载地址:http://xiazai.jb51.net/201611/yuanma/AndroidTimeLine(jb51.net).rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。