当前位置: 移动技术网 > 科技>人工智能>机器学习 > 自定义View练习之评分控件

自定义View练习之评分控件

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

自定义View练习系列

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

前言

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


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

一、示例

字母列表索引实力
评分控件示例

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

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

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

  • 继承并初始化
  • 自定义属性文件:
    需要准备两张图片作为点击与未点击,这个你自己定义 我这里用的是星星 可以直接右键保存下来
    在这里插入图片描述在这里插入图片描述
  • 测量当前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
位置图
在这里插入图片描述

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

<?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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网