欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

自定义View练习之评分控件

程序员文章站 2022-03-01 16:38:26
自定义View练习自定义View练习之评分控件,通讯录侧栏导航提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录自定义View练习前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结前言提示:这里可以添加本文要记录的大概内容:例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。提示:以下是本篇文章正文内容,下面案例可供参考一、pandas是什么?示例:pandas 是基于N...

自定义View练习系列

  1. 自定义View练习之评分控件

前言

写这篇文章主要方便自己理解记忆,如果说的不正确请大佬指出,在可以让我重新了解的同时也可以防止误导阅读人员


本文章练习是从这里Android字母索引列表学习的,有兴趣可以去这个大佬的教程,讲解清晰的

一、示例

自定义View练习之评分控件
评分控件示例

自定义View方法详解 onMeasure onDraw onLayout等方法就不解释了 搜索有很多,简单算是 重写这些方法,不过不是全部都要重写,视情况而定。 来实现我们需要的效果 TextView(1w+行代码,害怕),ImagView等其实都是自定义View只是官方写好给我们而已

  1. onMeasure() 测量宽高尺寸
  2. onDraw() 字面意思是绘制
  3. onLayout() 计算当前View以及子View的位置
  4. onTouch() 点击触摸交互
  5. 自定义属性

二、评分控件效果实现分析

  • 继承并初始化
  • 自定义属性文件:
    需要准备两张图片作为点击与未点击,这个你自己定义 我这里用的是星星 可以直接右键保存下来
    自定义View练习之评分控件自定义View练习之评分控件
  • 测量当前View宽高,并且同时判断评分几颗星,从而来决定最终宽高
  • 绘制,此处有图片,所以使用drawBitmap来绘制
  • 处理点击问题

2.1 继承View并初始化

代码如下(示例):

public class RatingStarsView extends View {

     /**
     *构造函数会在代码里new的时候调用
     *RatingStarsView rsv = new RatingStarsView (this);
     **/
    public RatingStarsView(Context context) {
        // super(context);
        // 无论怎么调用都会到第二个构造方法去
        this(context,null);
    }
    
	//在布局layout会调用这里
    public RatingStarsView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
	//在布局layout中使用(调用)但会有style
    public RatingStarsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //配置自定义属性,并且初始化我们需要的对象
        init(context, attrs);
    }
     /**
     * @param context 
     * @param attrs
     */
    private void init(Context context, @Nullable AttributeSet attrs) {
    
    }
}

2.2 自定义属性

自定义属性需要在attrs.xml文件里添加,没有就新建attrs.xml
位置图
自定义View练习之评分控件

属性文件配置代码(示例):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--name填我们刚才新建的View名称-->
    <declare-styleable name="RatingStarsView">
        <!-- 未选中图片 -->
        <attr name="normalStar" format="reference"/>
        <!-- 选中图片 -->
        <attr name="selectedStar" format="reference"/>
        <!-- 自定义评分数量 -->
        <attr name="starCount" format="integer"/>
    </declare-styleable>
 </resources>

layout-XML文件中调用:

    <文件路径.RatingStarsView
        app:starCount="10"
        app:normalStar="@mipmap/ic_star_normal"
        app:selectedStar="@mipmap/ic_star_selected"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

代码说明,

  • name
    attr 中的name就是我们调用的名字,比如TextView中text,textColor…这些,同时我们自定义的名字不能与系统属性名已经有的冲突,不能自定义background等我们常用的系统属性。
  • format
    取值类型format,这里特别说明下

“reference” //引用 资源文件
“color” //颜色
“boolean” //布尔值
“dimension” //尺寸值
“float” //浮点值
“integer” //整型值
“string” //字符串
“fraction” //百分数,比如200%

前面代码normalStar和selectedStar 取值类型我们都是填reference,因为这里是引用图片资源的,而starCount我们需要填数量,所以设置为integer。

属性定义也可以指定多种类型:
代码如下(示例):

  <attr name="TestBackground" format="reference|color" /> 

XML文件中调用:

 android:background = "@drawable/图片ID|#00FF00" 

回到我们开始新建的RatingStarsView文件 这里我设置了默认评分数量为3,默认图片为上面我放的图片,并设置图片宽高,最终配置代码示例


    private Bitmap mStarNormalBitmap;

    private Bitmap mStarSelectedBitmap;

    private int startCount = 3; // 默认3颗
/**
     * @param context
     * @param attrs
     */
    private void init(Context context, @Nullable AttributeSet attrs) {
       //获取自定义属性文件
       TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatingStarsView);
        /** 如果不设置默认图标 设置为0  如果为0时 说明用户没有设置 抛出异常提示用户
         *	array.getResourceId(R.styleable.RatingStarsView_normalStar, 0)
         *         if (starNormalId == 0 ) {
         *             throw new RuntimeException("请设置图形 startNormal");
         *         }
         * */
        int starNormalId = array.getResourceId(R.styleable.RatingStarsView_normalStar, R.mipmap.ic_star_normal);
        //这里将资源图片获取并设置图片尺寸
        mStarNormalBitmap = setImgSize(BitmapFactory.decodeResource(getResources(), starNormalId), starSize);
        
        int starSelectedId = array.getResourceId(R.styleable.RatingStarsView_selectedStar, R.mipmap.ic_star_selected);
        mStarSelectedBitmap = setImgSize(BitmapFactory.decodeResource(getResources(), starSelectedId), starSize);
        //获得自定义评分数量,没填默认为3 
        startCount = array.getInt(R.styleable.RatingStarsView_starCount, startCount);
		//千万不要忘了回收
        array.recycle();
        //如果有画笔Patin可以在这里初始化,不过当前不需要用到所以每填
    }

    /**
     * 修改图片宽高
     *
     * @param bm
     * @param starSize
     * @return
     */
    public Bitmap setImgSize(Bitmap bm, float starSize) {
        // 获得图片的宽高.
        int width = bm.getWidth();
        int height = bm.getHeight();
        // 计算缩放比例.
        float scaleWidth = starSize / width;
        float scaleHeight = starSize / height;
        // 取得想要缩放的matrix参数.
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        // 得到新的图片.
        return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
    }

2.3 onMeasure()测量并确认View宽高

MeasureSpec的三个模式

  1. UNSPECIFIED: 未加规定的,表示没有给子view添加任何规定。
  2. EXACTLY: 精确的,表示设置了精确值 android:layout_width=“40dp” match_parent也算
  3. AT_MOST: 子view可以在指定的尺寸内尽量大。wrap_content

当前只需要用到EXACTLY 如果设置了精确值,

代码如下(示例):


    private float starSize = dp2px(25);//默认设置当前评分星星图片尺寸为25dp

    private float starMargin = dp2px(5);//默认设置评分图片间距5dp

    private int currStarCount = 0; //默认当前评分为0


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取当前设置宽高模式 
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		//获取当前的宽高
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(widthMeasureSpec);
        //获取当前默认View的宽 (星星尺寸+间距)*数量 - 一个间距 
        int currWidth = (int) ((starSize + starMargin) * startCount - starMargin);
        /** 如果当前宽 为精确的值包括match_parent 重新设置星星的宽高*/
        if (widthMode == MeasureSpec.EXACTLY) {
            /** 如果当前宽小于默认宽,则重新设置星星宽高*/
            if (currWidth > width) {
                starMargin = dp2px(2);
                starSize = width / startCount - starMargin;
                mStarNormalBitmap = setImgSize(mStarNormalBitmap, starSize);
                mStarSelectedBitmap = setImgSize(mStarSelectedBitmap, starSize);
            }
        }
        //如果设置wrap_content 则设置宽为默认宽 
        if (widthMode == MeasureSpec.AT_MOST) {
            width = currWidth;
        }
		//高设置了精确值,默认都为40dp
        if (heightMode == MeasureSpec.AT_MOST) {
            height = (int) dp2px(40);
        }
		//确认宽高
        setMeasuredDimension(width, height);

    }
    //dp转px
    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,
                Resources.getSystem().getDisplayMetrics());
    }

2.4 onDraw()绘制

这里绘制主要确认每个星星的位置 再通过currStarCount判断是前几个星星是选中后几个是未选中
drawBitmap()方法 ,
第一个参数就是图片
第二个是x轴开始位置,每个星星左上角顶点开始的位置
第三个是0 因为上下不移动
第四个是画笔,当前不需要

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < startCount; i++) {
            //星星尺寸加间距 * 位置 获取x
            int x = (int) (i * (starSize + starMargin));
            //如果当前评分数大于第i个位置 图片为选中
            if (currStarCount > i) {
                canvas.drawBitmap(mStarSelectedBitmap, x, 0, null);
            } else {
                canvas.drawBitmap(mStarNormalBitmap, x, 0, null);
            }
        }
    }

2.5 onTouchEvent()点击交互


    @Override
    public boolean onTouchEvent(MotionEvent event) {
    	//获取用户点击模式
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN://按下
            case MotionEvent.ACTION_MOVE://移动
//            case MotionEvent.ACTION_UP: //抬起
				//获取点击相对位置
                float moveX = event.getX(); //getX():相对控件位置 getRawX(): 相对屏幕位置
       			//计算出点击评分星星数  
                int starCount = (int) (moveX / (starSize + starMargin)) + 1;
                //因为存在负数 如果当前星星数量小于0设置为0
                if (count < 0) {
                    count = 0;
                }
				//如果点击数大于默认数量 设置为默认数量
                if (count > startCount) {
                    count = startCount;
                }
                //如果点击数与当前相同 不重新绘制 
                if (count == currStarCount) {
                    return true;
                }
                //赋值
                currStarCount = count;
                //重新绘制
                invalidate();

        }
        //不能返回false 这里返回值可以百度搜索 onTouchEvent返回值详解
        return true;

    }

四、最终代码


/**
 * <自定义评分控件> <功能详细描述>
 *
 * @author HABIN
 * @version 2020/11/17
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class RatingStarsView extends View {

    private static final String TAG = "RatingStarsView.class";

    private Bitmap mStarNormalBitmap;

    private Bitmap mStarSelectedBitmap;

    private int startCount = 3; // 默认3颗

    private float starSize = dp2px(25);

    private float starMargin = dp2px(5);

    private int currStarCount = 0;

    
    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,
                Resources.getSystem().getDisplayMetrics());
    }
    
    public RatingStarsView(Context context) {
        this(context, null);
    }

    public RatingStarsView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RatingStarsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init(context, attrs);
    }


    /**
     * @param context
     * @param attrs
     */
    private void init(Context context, @Nullable AttributeSet attrs) {
    
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatingStarsView);
        
        int starNormalId = array.getResourceId(R.styleable.RatingStarsView_normalStar, R.mipmap.ic_star_normal);
        mStarNormalBitmap = setImgSize(BitmapFactory.decodeResource(getResources(), starNormalId), starSize);
        
        int starSelectedId = array.getResourceId(R.styleable.RatingStarsView_selectedStar, R.mipmap.ic_star_selected);
        mStarSelectedBitmap = setImgSize(BitmapFactory.decodeResource(getResources(), starSelectedId), starSize);
        
        startCount = array.getInt(R.styleable.RatingStarsView_starCount, startCount);

        array.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(widthMeasureSpec);
        
        int currWidth = (int) ((starSize + starMargin) * startCount - starMargin);

        if (widthMode == MeasureSpec.EXACTLY) {
            if (currWidth > width) {
                starMargin = dp2px(2);
                starSize = width / startCount - starMargin;
                mStarNormalBitmap = setImgSize(mStarNormalBitmap, starSize);
                mStarSelectedBitmap = setImgSize(mStarSelectedBitmap, starSize);
            }
        }
        if (widthMode == MeasureSpec.AT_MOST) {
            width = currWidth;
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            height = (int) dp2px(40);
        }

        setMeasuredDimension(width, height);

    }

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

        for (int i = 0; i < startCount; i++) {
            int x = (int) (i * (starSize + starMargin));
            if (currStarCount > i) {
                canvas.drawBitmap(mStarSelectedBitmap, x, 0, null);
            } else {
                canvas.drawBitmap(mStarNormalBitmap, x, 0, null);
            }

        }

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
       switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN://按下
            case MotionEvent.ACTION_MOVE://移动
//            case MotionEvent.ACTION_UP: //抬起
                float moveX = event.getX(); //getX():相对控件位置 getRawX(): 相对屏幕位置
                Log.e(TAG, "moveX ->: " + moveX);

                int count = (int) (moveX / (starSize + starMargin)) + 1;
                Log.e(TAG, "currStarCount =: " + currStarCount);
                if (count < 0) {
                    count = 0;
                }
          		if (count > startCount) {
                    count = startCount;
                }
                if (count == currStarCount) {
                    return true;
                }
                currStarCount = count;

                Log.e(TAG, "invalidate: " + "==");

                invalidate();

        }
        return true;

    }

    /**
     * 修改图片宽高
     *
     * @param bm
     * @param starSize
     * @return
     */
    public Bitmap setImgSize(Bitmap bm, float starSize) {
        // 获得图片的宽高.
        int width = bm.getWidth();
        int height = bm.getHeight();
        // 计算缩放比例.
        float scaleWidth = starSize / width;
        float scaleHeight = starSize / height;
        // 取得想要缩放的matrix参数.
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        // 得到新的图片.
        return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
    }
}

本文地址:https://blog.csdn.net/bingeho/article/details/110194365