星星自界说View评分作用结束

前语

在前面的学习中,咱们根本了解了一些 Canvas 的制作,那么这一章咱们一起温习一下图片的制作几种方式,和事情的简略交互方式。

咱们从易到难,作为基础的进阶控件,咱们从最简略的交互开端,那就自界说一个星星评分的控件吧。

一个 App 必不可少的评论系统打分的控件,能够展示评分,能够点击评分,能够滑动评分。它的结束总体上能够分为以下的步骤:

  1. 强制丈量巨细为咱们指定的巨细
  2. 先制作Drawable未评分的图片
  3. 在制作Bitmap已评分的图片
  4. 在onTouch中点击和移动的事情中动态核算当时的评分,从而刷新布局
  5. 回调的处理与特点的抽取

思路咱们现已有了,下面一步一步的来结束吧。

话不多说,Let’s go

Android自定义View的交互,往往都是从星星开始

1、丈量与图片的制作

咱们需求制作几个星星,那么咱们必须要设置的几个特点:

当时的评分值,总共有几个星星,每一个星星的距离和巨细,选中和未选中的Drawable图片:

    private int mStarDistance = 0;
    private int mStarCount = 5;
    private int mStarSize = 20;    //每一个星星的宽度和高度是共同的
    private float mScoreNum = 0.0F;  //当时的评分值
    private Drawable mStarScoredDrawable;  //现已评分的星星图片
    private Drawable mStarUnscoredDrawable;  //还未评分的星星图片
    private void init(Context context, AttributeSet attrs) {
        mScoreNum = 2.1f;
        mStarSize = context.getResources().getDimensionPixelSize(R.dimen.d_20dp);
        mStarDistance = context.getResources().getDimensionPixelSize(R.dimen.d_5dp);
        mStarScoredDrawable = context.getResources().getDrawable(R.drawable.iv_normal_star_yellow);
        mStarUnscoredDrawable = context.getResources().getDrawable(R.drawable.iv_normal_star_gray);
    }

丈量布局的时分,咱们就不能依据xml设置的 match_parent 或 wrap_content 来设置宽高,咱们需求依据星星的巨细与距离来动态的核算,所以不管xml中怎么设置,咱们都强制性的运用咱们自己的丈量。

星星的数量 * 星星的宽度再加上中间的距离 * 数量-1,便是咱们的控件宽度,控件高度则是星星的高度。

具体的确定丈量咱们再上一篇现已具体的温习过了,这儿直接贴代码:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(mStarSize * mStarCount + mStarDistance * (mStarCount - 1), mStarSize);
    }

这样就能够得到对应的丈量宽高 (加一个布景便利看作用):

Android自定义View的交互,往往都是从星星开始

怎么制作星星?直接制作Drawable即可,默许的Drawable的制作为:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mStarCount; i++) {
            mStarUnscoredDrawable.setBounds((mStarDistance + mStarSize) * i, 0, (mStarDistance + mStarSize) * i + mStarSize, mStarSize);
            mStarUnscoredDrawable.draw(canvas);
        }
    }

假如有5个星星图片,那么就为每一个星星定好位置:

Android自定义View的交互,往往都是从星星开始

那么现已选中的图片也需求运用这种办法制作吗?

核算当时的评分,然后核算核算需求制作多少星星,那么便是这样做:

    int score = (int) Math.ceil(mScoreNum);
    for (int i = 0; i < score; i++) {
        mStarScoredDrawable.setBounds((mStarDistance + mStarSize) * i, 0, (mStarDistance + mStarSize) * i + mStarSize, mStarSize);
        mStarScoredDrawable.draw(canvas);
    }

Android自定义View的交互,往往都是从星星开始

但是这么做不符合咱们的要求啊 ,咱们是需求是能够显示评分为2.5之类值,那么咱们怎么能制作半颗星呢?Drawable.draw(canvas) 的方式满足不了,那咱们能够运用 BitmapShader 的方式来制作。

初始化一个 BitmapShader 设置给 Paint 画笔,经过画笔就能够画出对应的形状。

比方此刻的场景,咱们假如想只画0.5个星星,那么咱们就能够

     paint = new Paint();
    paint.setAntiAlias(true);
    paint.setShader(new BitmapShader(drawableToBitmap(mStarScoredDrawable), BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
    @Override
    protected void onDraw(Canvas canvas) {
        for (int i = 0; i < mStarCount; i++) {
            mStarUnscoredDrawable.setBounds((mStarDistance + mStarSize) * i, 0, (mStarDistance + mStarSize) * i + mStarSize, mStarSize);
            mStarUnscoredDrawable.draw(canvas);
        }
         canvas.drawRect(0, 0, mStarSize * mScoreNum, mStarSize, paint);
    }

Android自定义View的交互,往往都是从星星开始

那么假如是大于一个星星之后的小数点就能够用公式核算

    if (mScoreNum > 1) {
        canvas.drawRect(0, 0, mStarSize, mStarSize, paint);
        if (mScoreNum - (int) (mScoreNum) == 0) {
            //假如评分是3.0之类的整数,那么直接按正常的rect制作
            for (int i = 1; i < mScoreNum; i++) {
                canvas.translate(mStarDistance + mStarSize, 0);
                canvas.drawRect(0, 0, mStarSize, mStarSize, paint);
            }
        } else {
            //假如是小数例如3.5,先制作之前的3个,再制作后边的0.5
            for (int i = 1; i < mScoreNum - 1; i++) {
                canvas.translate(mStarDistance + mStarSize, 0);
                canvas.drawRect(0, 0, mStarSize, mStarSize, paint);
            }
            canvas.translate(mStarDistance + mStarSize, 0);
            canvas.drawRect(0, 0, mStarSize * (Math.round((mScoreNum - (int) (mScoreNum)) * 10) * 1.0f / 10), mStarSize, paint);
        }
    } else {
        canvas.drawRect(0, 0, mStarSize * mScoreNum, mStarSize, paint);
    }

作用:

Android自定义View的交互,往往都是从星星开始

关于 BitmapShader 的其他用法,能够翻看我之前的自界说圆角圆形View,和自界说圆角容器的文章,里边都有用到过,主要是便利一些图片的裁剪和缩放等。

2、事情的交互与核算

这儿并没有涉及到什么事情嵌套,阻拦之类的复杂处理,只需求处理自身的 onTouch 即可。而咱们需求处理的便是按下的时分和移动的时分评分值的变化。

在onDraw办法中,咱们运用 mScoreNum 变量来制作的已评分的 Bitmap 制作。所以这儿咱们只需求在 onTouch 中核算出对应的 mScoreNum 值,让其重绘即可。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //x轴的宽度做一下最大最小的约束
        int x = (int) event.getX();
        if (x < 0) {
            x = 0;
        }
        if (x > mMeasuredWidth) {
            x = mMeasuredWidth;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE: {
                mScoreNum = x * 1.0f / (mMeasuredWidth * 1.0f / mStarCount);
                invalidate();
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
        }
        return super.onTouchEvent(event);
    }

核算出一颗星的长度,然后核算当时x轴的长度,就能够核算出当时有几颗星,咱们默许处理的是 float 类型。就能够依据核算出的 mScoreNum 值来得到对应的动画作用:

Android自定义View的交互,往往都是从星星开始

3. 回调处理与自界说特点抽取

到此作用的结束算是完毕了,但是咱们还有一些收尾作业没做,怎么监听进展的回调,怎么操控整数与浮点数的显示,是否支撑接触等等。然后对其做一些自界说特点的抽取,就能够在应用中比较广泛的运用了。

自界说特点:

    private int mStarDistance = 5;
    private int mStarCount = 5;
    private int mStarSize = 20;    //每一个星星的宽度和高度是共同的
    private float mScoreNum = 0.0F;  //当时的评分值
    private Drawable mStarScoredDrawable;  //现已评分的星星图片
    private Drawable mStarUnscoredDrawable;  //还未评分的星星图片
    private boolean isOnlyIntegerScore = false;  //默许显示小数类型
    private boolean isCanTouch = true; //默许支撑控件的点击
    private OnStarChangeListener onStarChangeListener;

自界说特点的赋值与初始化操作:

    private void init(Context context, AttributeSet attrs) {
        setClickable(true);
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.StarScoreView);
        this.mStarDistance = mTypedArray.getDimensionPixelSize(R.styleable.StarScoreView_starDistance, 0);
        this.mStarSize = mTypedArray.getDimensionPixelSize(R.styleable.StarScoreView_starSize, 20);
        this.mStarCount = mTypedArray.getInteger(R.styleable.StarScoreView_starCount, 5);
        this.mStarUnscoredDrawable = mTypedArray.getDrawable(R.styleable.StarScoreView_starUnscoredDrawable);
        this.mStarScoredDrawable = mTypedArray.getDrawable(R.styleable.StarScoreView_starScoredDrawable);
        this.isOnlyIntegerScore = mTypedArray.getBoolean(R.styleable.StarScoreView_starIsTouchEnable, true);
        this.isOnlyIntegerScore = mTypedArray.getBoolean(R.styleable.StarScoreView_starIsOnlyIntegerScore, false);
        mTypedArray.recycle();
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setShader(new BitmapShader(drawableToBitmap(mStarScoredDrawable), BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
    }

自界说特点的界说xml文件:

    <!--  评分星星控件  -->
    <declare-styleable name="StarScoreView">
        <!--星星距离-->
        <attr name="starDistance" format="dimension" />
        <!--星星巨细-->
        <attr name="starSize" format="dimension" />
        <!--星星个数-->
        <attr name="starCount" format="integer" />
        <!--星星已评分图片-->
        <attr name="starScoredDrawable" format="reference" />
        <!--星星未评分图片-->
        <attr name="starUnscoredDrawable" format="reference" />
        <!--是否能够点击-->
        <attr name="starIsTouchEnable" format="boolean" />
        <!--是否显示整数-->
        <attr name="starIsOnlyIntegerScore" format="boolean" />
    </declare-styleable>

在OnTouch的时分就能够判断是否能接触

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isCanTouch) {
            //x轴的宽度做一下最大最小的约束
            int x = (int) event.getX();
            if (x < 0) {
                x = 0;
            }
            if (x > mMeasuredWidth) {
                x = mMeasuredWidth;
            }
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_MOVE: {
                    setStarMark(x * 1.0f / (getMeasuredWidth() * 1.0f / mStarCount));
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    break;
                }
            }
            return super.onTouchEvent(event);
        } else {
            //假如设置不能点击,直接不触发事情
            return false;
        }
    }

而 setStarMark 则是设置入口的办法,内部判断是否支撑小数点和设置对于的监听,并调用重绘。

   public void setStarMark(float mark) {
        if (isOnlyIntegerScore) {
            mScoreNum = (int) Math.ceil(mark);
        } else {
            mScoreNum = Math.round(mark * 10) * 1.0f / 10;
        }
        if (this.onStarChangeListener != null) {
            this.onStarChangeListener.onStarChange(mScoreNum);  //调用监听接口
        }
        invalidate();
    }

一个简略的图片制作和事情接触的控件就结束啦,运用起来也是超级便利。

    <com.guadou.kt_demo.demo.demo18_customview.star.StarScoreView
        android:id="@+id/star_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="@dimen/d_40dp"
        android:background="#f1f1f1"
        app:starCount="5"
        app:starDistance="@dimen/d_5dp"
        app:starIsOnlyIntegerScore="false"
        app:starIsTouchEnable="true"
        app:starScoredDrawable="@drawable/iv_normal_star_yellow"
        app:starSize="@dimen/d_35dp"
        app:starUnscoredDrawable="@drawable/iv_normal_star_gray" />

Activity中能够设置评分和设置监听:

    override fun init() {
        val starView = findViewById<StarScoreView>(R.id.star_view)
        starView.setOnStarChangeListener {
            YYLogUtils.w("当时选中的Star:$it")
        }
        findViewById<View>(R.id.set_progress).click {
            starView.setStarMark(3.5f)
        }
    }

作用:

Android自定义View的交互,往往都是从星星开始

跋文

整个流程走下来是不是很简略呢,此控件不止用于星星类型的评分,任何图片资源都能够运用,现在咱们思路打开扩展一下,类似的场景和作用咱们能够结束一些图片进展,接触进展条,圆环的SeekBar,等等类似的操控都是类似的思路。

这一期的比较简略,我并没有上传到 Maven ,假如有需求能够去我的项目里边拿,假如有需求的话也能够自行修正,假如我们有爱好能够检查源码点击【传送门】。你也能够关注我的这个Kotlin项目,我有时间都会持续更新。

关于事情交互的自界说View后边有时间会再出略微复杂一点的,协助我们巩固与温习。我心里的道路是先学制作再学交互(由于交互的基础便是制作),然后再学ViewGroup的嵌套、阻拦、分发、排版等等,从易到难争取让我们温习个通透,当然假如有人看的话,我会持续更新。

常规,我如有讲解不到位或错漏的当地,期望同学们能够指出沟通。

假如感觉本文对你有一点点的启示,还望你能点赞支撑一下,你的支撑是我最大的动力。

Ok,这一期就此结束。

Android自定义View的交互,往往都是从星星开始

本文正在参加「金石计划 . 分割6万现金大奖」