Android自定义View实现星星评分效果

目录
  • 前言
  • 1、测量与图片的绘制
  • 2、事件的交互与计算
  • 3. 回调处理与自定义属性抽取
  • 后记

前言

在前面的学习中,我们基本了解了一些 Canvas 的绘制,那么这一章我们一起复习一下图片的绘制几种方式,和事件的简单交互方式。

我们从易到难,作为基础的进阶控件,我们从最简单的交互开始,那就自定义一个星星评分的控件吧。

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

  • 强制测量大小为我们指定的大小
  • 先绘制Drawable未评分的图片
  • 在绘制Bitmap已评分的图片
  • 在onTouch中点击和移动的事件中动态计算当前的评分,进而刷新布局
  • 回调的处理与属性的抽取

思路我们已经有了,下面一步一步的来实现吧。

话不多说,Let's go

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);
    }

这样就可以得到对应的测量宽高 (加一个背景方便看效果):

如何绘制星星?直接绘制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个星星图片,那么就为每一个星星定好位置:

那么已经选中的图片也需要使用这种方法绘制吗?

计算当前的评分,然后计算计算需要绘制多少星星,那么就是这样做:

    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);
    }

可是这么做不符合我们的要求啊 ,我们是需要是可以显示评分为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);
    }

那么如果是大于一个星星之后的小数点就可以用公式计算

    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);
    }

效果:

关于 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 值来得到对应的动画效果:

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)
        }
    }

效果:

后记

整个流程走下来是不是很简单呢,此控件不止用于星星类型的评分,任何图片资源都可以使用,现在我们思路打开扩展一下,相似的场景和效果我们可以实现一些图片进度,触摸进度条,圆环的SeekBar,等等类似的控制都是相似的思路。

到此这篇关于Android自定义View实现星星评分效果的文章就介绍到这了,更多相关Android自定义View星星评分内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android自定义View实现跟随手指移动

    对View的移动,实现的方法有好几种,原理是通过改变View的位置来移动View,下面来实现这样的效果 动画的方法 通过改变View的tranlationX和tranlationY的值来实现移动,首先来写一个自定义View类,重写onTouchEvent方法,实现构造方法 public class MyView extends View {     public MyView(Context context) {         super(context);     }     public

  • Android自定义view实现滚动选择控件详解

    目录 前言 需求 编写代码 主要问题 前言 上篇文章通过一个有header和footer的滚动控件(Viewgroup)学了下MeasureSpec.onMeasure以及onLayout,接下来就用一个滚动选择的控件(View)来学一下onDraw的使用,并且了解下在XML自定义控件参数. 需求 这里就是一个滚动选择文字的控件,还是挺常见的,之前用别人的,现在选择手撕一个,核心思想如下: 1.有三层不同大小及透明度的选项,选中项放在中间 2.接受一个列表的数据,静态时显示三个值,滚动时显示四个

  • Android小工具自定义view课表

    本文实例为大家分享了Android自定义view课表的具体代码,供大家参考,具体内容如下 这里是模拟课表,数据写死了的,不过也可以通过抓包获取教务系统课表 1.xml文件 <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="

  • Android自定义View仿大众点评星星评分控件

    本文实例为大家分享了Android仿大众点评星星评分控件的具体代码,供大家参考,具体内容如下 话不多说,直接上代码,这里采用的是自定View public class RatingBar extends View { // 正常.半个和选中的星星 private Bitmap mStarNormal, mStarHalf, mStarSelected; //星星的总数 private int mStartTotalNumber = 5; //选中的星星个数 private float mSele

  • Android自定义View实现进度条动画

    本文实例为大家分享了Android自定义View实现进度条动画的具体代码,供大家参考,具体内容如下 控件效果 原理: 控制代码/ /更新进度值 val mHandler = object : Handler() {         override fun handleMessage(msg: Message?) {             progressView.setCurrentProgress(progress1.toFloat())         }     } //开启动画,更新

  • Android自定义View实现星星评分效果

    目录 前言 1.测量与图片的绘制 2.事件的交互与计算 3. 回调处理与自定义属性抽取 后记 前言 在前面的学习中,我们基本了解了一些 Canvas 的绘制,那么这一章我们一起复习一下图片的绘制几种方式,和事件的简单交互方式. 我们从易到难,作为基础的进阶控件,我们从最简单的交互开始,那就自定义一个星星评分的控件吧. 一个 App 必不可少的评论系统打分的控件,可以展示评分,可以点击评分,可以滑动评分.它的实现总体上可以分为以下的步骤: 强制测量大小为我们指定的大小 先绘制Drawable未评分

  • Android自定义View实现弹性小球效果

    照例先看效果图 自定义代码示例 public class BezierView extends View { Paint paint;//画笔 Path path;//路径 int radius = 50;//圆的半径 int time = 100;//计数时长 int index; int offsetIndex; float viewX, viewY;//图形中心点坐标 float width;//屏幕宽度 float partWidth;//屏幕宽度的1/4 int paddingLeft

  • Android自定义View实现折线图效果

    下面就是结果图(每种状态用一个表情图片表示): 一.主页面的布局文件如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height=&quo

  • Android自定义VIew实现卫星菜单效果浅析

     一 概述: 最近一直致力于Android自定义VIew的学习,主要在看<android群英传>,还有CSDN博客鸿洋大神和wing大神的一些文章,写的很详细,自己心血来潮,学着写了个实现了类似卫星效果的一个自定义的View,分享到博客上,望各位指点一二.写的比较粗糙,见谅.(因为是在Linux系统下写的,效果图我直接用手机拍的,难看,大家讲究下就看个效果,勿喷). 先来看个效果图,有点不忍直视: 自定义VIew准备: (1)创建继承自View的类; (2)重写构造函数; (3)定义属性. (

  • Android自定义View实现拖拽效果

    腾讯QQ有那种红点拖动效果,今天就来实现一个简单的自定义View拖动效果,再回到原处,并非完全仿QQ红点拖动. 先来看一下效果图 简单说一下实现步骤 1.创建一个类继承View 2.绘制出一个小球 3.重写onTouchEvent,来根据手指放下,移动,抬起,来控制小球 4.直接在布局中引用 先贴一张图看下View的坐标系 下面就贴一下代码,最后会给出源码 public class CustomView extends View { private int lastX; private int

  • Android自定义view之围棋动画效果的实现

    前言 废话不多说直接开始 老规矩,文章最后有源码 完成效果图 棋子加渐变色 棋子不加渐变色 一.测量 1.获取宽高 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; useWidth = mWidth; if (mWidth > mHeight) { useWidth =

  • Android自定义view之3D正方体效果实例

    目录 前言 一.小提 二.将传感器改成事件分发机制 三.使用 四.源码 总结 前言 在之前写了一篇关于3D效果的文章,借助传感器展示,有小伙伴问可不可以改成手势滑动操作(事件分发),所以出一篇文章 传感器相关文章链接:Android 3D效果的实现 一.小提 相对于常见的自定义view而言,继承的GLSurfaceView只有两个构造函数.可以理解为没有提供获取自定义属性的方法. public TouchSurfaceView(Context context) { super(context);

  • Android自定义View实现标签流效果

    本文实例为大家分享了Android自定义View实现标签流效果的具体代码,供大家参考,具体内容如下 一.概述 Android自定义View实现标签流效果,一行放不下时会自动换行,用户可以自己定义单个标签的样式,可以选中和取消,可以监听单个标签的点击事件,功能还算强大,可以满足大部分开发需求,值得推荐,效果图如下: 二.实现代码 1.自定义View 定义属性文件 <declare-styleable name="FlowTagView">         <attr n

  • Android自定义View实现数字雨效果的全过程

    目录 效果图 实现步骤 总结 效果图 在安卓中多种类型的动画,有帧动画.补间动画.属性动画,除此之外,使用自定义的View结合数学公式,就可以绘制出复杂的界面或者动画.这篇文章记录的是仿照黑客帝国的数字雨,来看看效果吧. 实现步骤 准备工作,常量的配置信息 // 文字的颜色值 final int DEFAULT_TEXT_COLOR = Color.argb(255, 0, 255, 70); // 文字大小 final int TEXT_SIZE = 24; // 普通画笔 Paint mPa

  • android自定义View实现跑马灯效果

    android自带的TextView可以实现跑马灯效果,但是有很多的局限性:比如需要设置ellipsize="marquee",获取 focusable="true",设置singleLine="true",控件里的内容需要超过控件本身的长度,无法控制滚动速度和滚动暂停和继续滚动功能,各种限制导致用起来特别不顺手,几乎无法使用到生产环境中,在此背景下,需要自定义View实现跑马灯效果. 使用主要方法:自定义View重写onDraw方法,通过can

随机推荐