Android自定义View系列之进度指示控件

我开通微信公众号啦,如果大家喜欢我的文章,欢迎大家关注我的微信号,我会定期为大家推送Android中的热门知识。
二维码

今天为大家介绍另一个自定义View——进度指示器,这个在电商App和支付宝等中经常遇到。如在电商App中买一个东西会有如下步骤:
下订单——>支付完成——>已发货——>交易完成
先使用我们的自定义View来展示一下上面的步骤吧
这里写图片描述

如上图所示,步骤未完成时是灰色(可指定),当步骤完成时显示成绿色(可指定),并且最后一个完成的任务有光晕效果。
同样,我们看看这个自定义View是如何实现的吧。
由于需要自定义 “完成颜色”和“未完成颜色”等属性,所以我们需要定义如下属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="StepView">
        <attr name="lineheight" format="dimension"></attr>
        <attr name="smallradius" format="dimension"></attr>
        <attr name="largeradius" format="dimension"></attr>
        <attr name="undonecolor" format="color"></attr>
        <attr name="donecolor" format="color"></attr>
        <attr name="totalstep" format="integer"></attr>
        <attr name="completestep" format="integer"></attr>
    </declare-styleable>
</resources>

这些属性的意义如下:
lineheight :表示指示器中线条的高度
smallradius:表示指示器中圆点的半径
undonecolor:表示没有完成的步骤的颜色
donecolor:表示已经完成步骤的颜色
totalstep:表示总步骤数
completestep:表示已经完成的步骤

如果你还需要自定义其他属性,也可以在这里自行添加。
接下来我们看看代码的实现吧,我们分两部分实现:
1、StepBar部分,这部分主要用来显示进度条部分
2、SetpView部分,这部分主要依赖StepBar完成titile显示

在Android目前存在的View中,没有具备类似功能的View,所以我们不能通过继承某个View来实现,而是需要通过继承普通的View,并完成View绘制的三部曲(measure,layout,draw)。
首先看第一部曲:

    /**
     * View的绘制的第一阶段调用
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width=getDefaultWidth();
        if(MeasureSpec.UNSPECIFIED!=MeasureSpec.getMode(widthMeasureSpec)){
            width=MeasureSpec.getSize(widthMeasureSpec);
        }

        int height=120;
        if(MeasureSpec.UNSPECIFIED!=MeasureSpec.getMode(heightMeasureSpec)){
            height=MeasureSpec.getSize(heightMeasureSpec);
        }
        Log.d(TAG, "onMeasure-->width:" + width + " height:" + height);
        setMeasuredDimension(width, height);

    }

onMeasure 方法中,首先判断MeasureSpec 中的mode部分是否为MeasureSpec.UNSPECIFIED,如果是的,则宽度通过getDefaultWidth() 方法获取,高度为120,如果不是,那么拿到widthMeasureSpec/heightMeasureSpec中的size部分。如果你对于为什么这样做的原因不清楚,欢迎你阅读我的关于View绘制相关文章:
Android中View的绘制机制源码分析一
Android中View的绘制机制源码分析二
Android中View的绘制机制源码分析三
Android中View的绘制机制源码分析四

第二部曲:

    /*
     * 在View的绘制的第二阶段(layout)中,当尺寸发生变化时调用
     * 注意:第二阶段本来是调用onLayout方法,此方法是在onLayout方法中被调用
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //计算位置
        mCenterY=this.getHeight()/2;
        mLeftX=this.getLeft()+getPaddingLeft();
        mLeftY=mCenterY-mLineHeight/2;
        mRightX=this.getRight()-getPaddingRight();
        mRightY=mCenterY+mLineHeight/2;
        Log.d(TAG,"onSizeChanged->mLeftX:"+mLeftX);
        Log.d(TAG, "onSizeChanged->mRightX:" + mRightX);
        if(mTotalStep>1){
            mDistance=(mRightX-mLeftX)/(mTotalStep-1);
            Log.d(TAG,"onSizeChanged->mDistance:"+mDistance);
        }
    }

本来第二部应该改写的时onDraw() 方法的,但这里改写的却是onSizeChange() 方法,由于这里我只关注View的大小改变,并且onSizeChange() 方法是在 onDraw() 中被调用(只有尺寸发生变化才会调用),在onSizeChange() 方法中我们计算了自定义View的纵向中心位置,左边坐标,右边左边,小圆点之间的距离等等。在这里可以思考一下,为啥要在这里计算尺寸相关变量?这个问题我已经在View的绘制机制相关文章做出了解答。

下面看看最重要的第三部曲:


    /**
     * View的绘制的第三阶段调用
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mTotalStep<=0 || mCompleteStep<0 || mCompleteStep>mTotalStep){
            return;
        }
        Paint mCirclePaint=new Paint();
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setStyle(Paint.Style.FILL);
        mCirclePaint.setColor(mUnDoneColor);

        canvas.drawRect(mLeftX,mLeftY,mRightX,mRightY,mCirclePaint);
        float xLoc=mLeftX;
        //画所有的步骤(圆形)
        for(int i=0;i<mTotalStep;i++){
            canvas.drawCircle(xLoc, mLeftY, mSmallRadius, mCirclePaint);
            xLoc=xLoc+mDistance;
        }

        //画已经完成的步骤(圆形加矩形)
        xLoc=mLeftX;
        mCirclePaint.setColor(mDoneColor);
        for(int i=0;i<mCompleteStep;i++){
            if(i>0){
                canvas.drawRect(xLoc-mDistance,mLeftY,xLoc,mRightY,mCirclePaint);
            }
            canvas.drawCircle(xLoc, mLeftY, mSmallRadius, mCirclePaint);


            //画当前步骤(加光晕效果)
            if(i==mCompleteStep-1){
                mCirclePaint.setColor(getTranspartColorByAlpha(mDoneColor,0.2f));
                canvas.drawCircle(xLoc, mLeftY, mLargeRadius, mCirclePaint);
            }else {
                xLoc=xLoc+mDistance;
            }

        }
    }

onDraw() 方法中,主要是对UI进行绘制,分为三个步骤:

  1. 绘制横线
  2. 根据总步骤的个数,绘制小圆点
  3. 绘制已经完成步骤的小圆点(颜色和未完成的步骤不同),并未最后一个完成步骤添加光晕效果

StepBar的主要逻辑已经介绍完了,下面介绍一下SetpView的逻辑,在使用的时候,我们只会使用SetpView,SetpView是对SetpBar的一个封装,并提供了title的属性。

SetpView的UI布局:

<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >
    <FrameLayout
        android:id="@+id/step_title"
        android:layout_gravity="left"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
    </FrameLayout>
    <com.gavin.step.StepBar
        android:id="@+id/step_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/step_title"
        android:paddingLeft="30dp"
        android:paddingRight="30dp"
        />
</merge>

这个布局很简单,相信不用我介绍,我们直接看SetpView的代码部分吧

public StepView(Context context) {
        super(context);
        init(context,null,0);
    }

    public StepView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public StepView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public StepView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context,attrs,defStyleAttr);
    }

    private void init(Context mContext,AttributeSet attrs,int defStyleAttr){
        LayoutInflater.from(mContext).inflate(R.layout.step_view,this,true);
        mStepBar=(StepBar)this.findViewById(R.id.step_bar);
        mTitleGroup=(FrameLayout)this.findViewById(R.id.step_title);
        TypedArray array = mContext.obtainStyledAttributes(attrs,R.styleable.StepView,defStyleAttr,0);

        mStepBar.setLineHeight(array.getDimensionPixelOffset(R.styleable.StepView_lineheight, StepBar.DEFAULT_LINE_HEIGHT));
        mStepBar.setSmallRadius(array.getDimensionPixelOffset(R.styleable.StepView_smallradius, StepBar.DEFAULT_SMALL_CIRCLE_RADIUS));
        mStepBar.setLargeRadius(array.getDimensionPixelOffset(R.styleable.StepView_largeradius, StepBar.DEFAULT_LARGE_CIRCLE_RADIUS));
        mStepBar.setUnDoneColor(array.getColor(R.styleable.StepView_undonecolor, StepBar.COLOR_BAR_UNDONE));
        mStepBar.setDoneColor(array.getColor(R.styleable.StepView_undonecolor, StepBar.COLOR_BAR_DONE));
        mStepBar.setTotalStep(array.getInteger(R.styleable.StepView_totalstep, 0));
        mStepBar.setCompleteStep(array.getInteger(R.styleable.StepView_completestep, 0));

        //在StepBar布局完成之后开始添加title
        mStepBar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){

            @Override
            public void onGlobalLayout() {
                initStepTitle();
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                    mStepBar.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                } else {
                    mStepBar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
            }
        });
        array.recycle();
    }

这里我们主要看看init() 方法,这个方法是在构造函数中被调用,主要用来解析自定义属性的,关于自定义属性的逻辑我不在多说,这里主要看为SetpBar添加title的逻辑,我并没有直接在init() 方法中调用添加title的逻辑,而是等SetpBar第二部曲完成之后才添加title的逻辑。为什么呢?先看看添加title的逻辑再来解答此问题吧

private void initStepTitle(){
        if(mStepTitles==null){
            return;
        }
        mTitleGroup.removeAllViews();

        if(mStepTitles.size()!=mStepBar.getTotalStep()){
            throw new IllegalStateException("设置的Title的个数和步骤数不一致!");
        }
        int stepNum=mStepBar.getTotalStep();
        for(int i=1;i<=stepNum;i++){
            final float stepPos=mStepBar.getPositionByStep(i);
            final TextView title=new TextView(this.getContext());
            title.setText(mStepTitles.get(i - 1));
            title.setSingleLine();
            title.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    title.setTranslationX(stepPos - title.getMeasuredWidth() / 2);
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                        title.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                        title.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }
                }
            });
            FrameLayout.LayoutParams lp=new FrameLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);
            mTitleGroup.addView(title,lp);
        }
    }

由于在添加title的时候,需要用到小圆点的位置,这个位置只有SetpBar的第二部曲完成之后才可以确定,这就是为什么在刚才需要在SetpBar第二部曲完成之后才能添加title.

好了,步骤指示器的代码逻辑介绍完了,如果有什么问题欢迎留言讨论…
代码下载地址

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Android自定义View是指基于Android原生控件的一种扩展,可以根据自己的需求和设计规范来创建更加个性化和独特的控件。而歌词控件是一种针对音乐播放器或者视频播放器等应用场景中的需求,用于显示音乐或者视频的歌词的控件Android自定义View歌词控件的实现思路如下: 1. 首先需要自定义一个View,并继承自View或者其子类,如TextView。 2. 在自定义View中重写onDraw方法,在其中实现绘制歌词的逻辑。 3. 在onDraw方法中,使用Canvas对象进行绘制,可以使用drawText方法绘制歌词文本,也可以使用drawBitmap方法绘制图片背景等。 4. 可以通过自定义属性,如字体大小、字体颜色、歌词滚动速度等,来对歌词控件进行配置。 5. 如果需要实现歌词的滚动效果,可以使用ValueAnimator或者Scroller来实现歌词的平滑滚动。 6. 如果需要实现点击歌词跳转播放进度的功能,可以通过添加点击事件监听器,在触摸事件中判断点击位置对应的歌词行,并根据歌词的时间戳跳转到指定的播放进度。 总结来说,Android自定义View歌词控件的实现需要重写onDraw方法进行绘制,可以通过Canvas对象进行绘制文本或者图像,通过自定义属性进行配置,使用动画或者滚动实现歌词的平滑滚动,通过监听触摸事件实现点击歌词跳转播放进度的功能。通过以上步骤,我们可以创建一个个性化的歌词控件,满足不同应用场景的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值