Android——仿优酷带时间点的进度条

最近项目中碰到一条新需求:播放器的进度条上显示片头片尾,关键帧和关键片段打点,如下图:

且关键帧开始时间用白色点,结束时间用黄色点。

一开始在github上面找了几个不错的项目,比如:BubbleSeekBarIndicatorSeekBar,可是这两个项目都没办法直接拿来用,后来同事叫我自己画打点内容,额,一开始有点偷懒觉得画太复杂了,有现成的多好,后来实在没找到合适的开源库,愣是硬着头皮自己写了个。。。下面就来说说实现过程吧。

一般来说自定义的打点SeekBar他们都是自己重写了一套SeekBar的内容,而我想偷懒啊,所以我直接继承了SeekBar,在SeekBar上面画打点效果,但是要知道画布是分层的,这样子的话,我就是在SeekBar的最上层画内容了,这会导致当打点圆点和拖动滑动重合的时候,打点圆点会显示在拖动滑块上面,这显然不是我们想要的效果,所以怎么办呢?鄙人没想到好方法,参考了下API25中SeekBar的 android:tickMark属性的实现方式,也很简单,就是在SeekBar的onDraw方法中重新绘制内容,但是你会发现滑块是显示在TickMark上面的,这里可能是由于重写了drawableStateChanged方法吧,但是后来没找到当滑块和tickMark重合的处理逻辑,待以后验证吧。说了这么多你肯定觉得晕,那看代码吧,一目了然。

AppCompatSeekBar.java

public class AppCompatSeekBar extends SeekBar {

    private AppCompatSeekBarHelper mAppCompatSeekBarHelper;

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

    public AppCompatSeekBar(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.seekBarStyle);
    }

    public AppCompatSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mAppCompatSeekBarHelper = new AppCompatSeekBarHelper(this);
        mAppCompatSeekBarHelper.loadFromAttributes(attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mAppCompatSeekBarHelper.drawTickMarks(canvas);
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        mAppCompatSeekBarHelper.drawableStateChanged();
    }

    @RequiresApi(11)
    @TargetApi(11)
    @Override
    public void jumpDrawablesToCurrentState() {
        super.jumpDrawablesToCurrentState();
        mAppCompatSeekBarHelper.jumpDrawablesToCurrentState();
    }
}

drawTickMarks()方法实现:

    /**
     * Draw the tick marks.
     */
    void drawTickMarks(Canvas canvas) {
        if (mTickMark != null) {
            final int count = mView.getMax();
            if (count > 1) {
                final int w = mTickMark.getIntrinsicWidth();
                final int h = mTickMark.getIntrinsicHeight();
                final int halfW = w >= 0 ? w / 2 : 1;
                final int halfH = h >= 0 ? h / 2 : 1;
                mTickMark.setBounds(-halfW, -halfH, halfW, halfH);

                final float spacing = (mView.getWidth() - mView.getPaddingLeft()
                        - mView.getPaddingRight()) / (float) count;
                final int saveCount = canvas.save();
                canvas.translate(mView.getPaddingLeft(), mView.getHeight() / 2);
                for (int i = 0; i <= count; i++) {
                    mTickMark.draw(canvas);
                    canvas.translate(spacing, 0);
                }
                canvas.restoreToCount(saveCount);
            }
        }
    }

drawableStateChanged()方法实现:

 void drawableStateChanged() {
        final Drawable tickMark = mTickMark;
        if (tickMark != null && tickMark.isStateful()
                && tickMark.setState(mView.getDrawableState())) {
            mView.invalidateDrawable(tickMark);
        }
    }

从上面代码可以看出,drawableStateChanged()方法其实是在重绘View了。当View的状态发生改变的时候都会调用这个方法重绘view,从源码中可以看出,setSelected(),setActivated(),拖动View,焦点改变,setEnabled(),setPressed(),onWindowFocusChanged(),setHovered(),dispatchAttachedToWindow()等等都会回调,所以当我们拖拽SeekBar的时候就会触发这个方法,这时候重绘SeekBar就可以重绘View,并判断他们是否重合,如果重合则不绘制打点圆点,从而解决打点圆点悬浮在拖动滑块上面的内容的问题。

整理思路和遇到的问题如何解决已经说完了,下面看看代码实现和使用吧。

TickSeekBar代码实现:

public class TickSeekBar extends AppCompatSeekBar {
    private int mPaddingLeft;
    private float mSeekBlockLength;//每个片段的宽度
    private float mTrackY;//时间点y坐标
    private int mMaxProgress;//进度条最大进度
    private float mThumbCenterX;//滑块位置
    private float mLeft, mRight;//seekbar的左右位置
    private int mMeasureHeight;

    //节点时间
    //节点大小--属性
    private int mTickWith;//单位:dp
    private int mTickHeight;
    //自动吸附--属性
    private boolean isAutoAdjustTick;
    //画笔
    private Paint mStockPaint;
    private List<TickData> mTickDataList;

    public TickSeekBar(Context context) {
        super(context);
    }

    public TickSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAttrs(context, attrs);
        initStrokePaint();
    }

    public TickSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //初始化属性
    private void initAttrs(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TickSeekBar);
        mTickWith = ta.getInt(R.styleable.TickSeekBar_tick_with, 2);
        mTickHeight = ta.getInt(R.styleable.TickSeekBar_tick_height, 2);
        isAutoAdjustTick = ta.getBoolean(R.styleable.TickSeekBar_auto_adjust_tick, true);
        ta.recycle();
    }

    //初始化画笔
    private void initStrokePaint() {
        if (mStockPaint == null) {
            mStockPaint = new Paint();
        }
        mStockPaint.setAntiAlias(true);
    }

    private void initSeekBarInfo() {
        int mMeasuredWidth = getMeasuredWidth();
        mMeasureHeight = getMeasuredHeight();
        mPaddingLeft = getPaddingLeft();
        int mPaddingRight = getPaddingRight();
        int mPaddingTop = getPaddingTop();
        float mSeekLength = mMeasuredWidth - mPaddingLeft - mPaddingRight;
        mSeekBlockLength = mSeekLength / mMaxProgress;
        mTrackY = mPaddingTop;
        mMaxProgress = getMax();
        mLeft = getPaddingLeft();
        mRight = getMeasuredWidth() - getPaddingRight();
    }

    private void initTickLocation(List<TickData> tickDataList) {
        if (mTickDataList == null) {
            mTickDataList = new ArrayList<>();
        } else {
            mTickDataList.clear();
        }
        mTickDataList = tickDataList;
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawTicks(canvas);
    }

    private void drawTicks(Canvas canvas) {
        if (mTickDataList == null || mTickDataList.size() == 0) {
            return;
        }

        for (int i = 0; i < mTickDataList.size(); i++) {
            float locationX = mSeekBlockLength * mTickDataList.get(i).getLocation();
//            if (getThumbPosOnTick() == i) {//Seekbar滑块和tick点重合则不绘制tick点
//                continue;
//            }

            int rectWidth = mTickWith;
            float top = mTrackY + mMeasureHeight / 2f - mTickWith / 2f;
            RectF roundRect = new RectF(locationX - rectWidth, top, locationX + rectWidth, top +
                    mTickHeight);
            mStockPaint.setColor(getResources().getColor(mTickDataList.get(i).getColor()));
            canvas.drawRoundRect(roundRect, 5, 5, mStockPaint);
        }
    }

    //设置时间点
    public void setTicks(List<TickData> tickDataList) {
        initStrokePaint();
        initTickLocation(tickDataList);
        //requestLayout();
        invalidate();
    }

    public static class TickData {
        private float location;
        private int color;

        public TickData(float location, int color) {
            this.location = location;
            this.color = color;
        }

        public float getLocation() {
            return location;
        }

        public void setLocation(float location) {
            this.location = location;
        }

        public int getColor() {
            return color;
        }

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

TickSeekBar的使用:
xml文件使用:

<com.demo.customseekbar.TickSeekBar
        android:id="@+id/tickSeekbar2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:max="100"
        android:maxHeight="2dp"
        android:progress="10"
        android:progressDrawable="@drawable/seekbar"
        android:thumb="@drawable/thumb"
        android:thumbOffset="2dp"
        app:auto_adjust_tick="true"
        app:tick_height="6"
        app:tick_with="8"/>

java代码实现:

tickSeekBar = (TickSeekBar) findViewById(R.id.tickSeekbar2);
 List<CustomSeekBar.TickData> tickDataList = new ArrayList<>();
        CustomSeekBar.TickData tickData1 = new CustomSeekBar.TickData(10, android.R.color.white);
        tickDataList.add(tickData1);
        CustomSeekBar.TickData tickData2 = new CustomSeekBar.TickData(45, android.R.color
                .holo_orange_dark);
        tickDataList.add(tickData2);
        CustomSeekBar.TickData tickData3 = new CustomSeekBar.TickData(60, android.R.color.white);
        tickDataList.add(tickData3);
        CustomSeekBar.TickData tickData4 = new CustomSeekBar.TickData(90, android.R.color
                .holo_orange_dark);
        tickDataList.add(tickData4);
        customSeekBar.setTicks(tickDataList);

注意:
一定要设置android:max,但这个max不一定是100,也可以自定义自己的规则,例如:视频的长度;
customSeekBar.setTicks(tickDataList);可以多次调用改变打点的位置和颜色哦。

猜你喜欢

转载自blog.csdn.net/u012230055/article/details/80015221