自定义View练习之评分控件
自定义View练习系列
自定义View练习之评分控件,通讯录侧栏导航
前言
写这篇文章主要方便自己理解记忆,如果说的不正确请大佬指出,在可以让我重新了解的同时也可以防止误导阅读人员
本文章练习是从这里Android字母索引列表学习的,有兴趣可以去这个大佬的教程,讲解清晰的
一、示例
评分控件示例
自定义View方法详解 onMeasure onDraw onLayout等方法就不解释了 搜索有很多,简单算是 重写这些方法,不过不是全部都要重写,视情况而定。 来实现我们需要的效果 TextView(1w+行代码,害怕),ImagView等其实都是自定义View只是官方写好给我们而已
- onMeasure() 测量宽高尺寸
- onDraw() 字面意思是绘制
- onLayout() 计算当前View以及子View的位置
- onTouch() 点击触摸交互
- 自定义属性
二、评分控件效果实现分析
- 继承并初始化
- 自定义属性文件:
需要准备两张图片作为点击与未点击,这个你自己定义 我这里用的是星星 可以直接右键保存下来
- 测量当前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的三个模式
- UNSPECIFIED: 未加规定的,表示没有给子view添加任何规定。
- EXACTLY: 精确的,表示设置了精确值 android:layout_width=“40dp” match_parent也算
- 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
推荐阅读