星星自界说View评分作用结束
前语
在前面的学习中,咱们根本了解了一些 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,等等类似的操控都是类似的思路。
这一期的比较简略,我并没有上传到 Maven ,假如有需求能够去我的项目里边拿,假如有需求的话也能够自行修正,假如我们有爱好能够检查源码点击【传送门】。你也能够关注我的这个Kotlin项目,我有时间都会持续更新。
关于事情交互的自界说View后边有时间会再出略微复杂一点的,协助我们巩固与温习。我心里的道路是先学制作再学交互(由于交互的基础便是制作),然后再学ViewGroup的嵌套、阻拦、分发、排版等等,从易到难争取让我们温习个通透,当然假如有人看的话,我会持续更新。
常规,我如有讲解不到位或错漏的当地,期望同学们能够指出沟通。
假如感觉本文对你有一点点的启示,还望你能点赞
支撑一下,你的支撑是我最大的动力。
Ok,这一期就此结束。
本文正在参加「金石计划 . 分割6万现金大奖」