桥下红药

机器应该工作、人类应该思考

自定义View – 星星评分控件

Android, 总分类 0 评

需求是这样子的,分析下可以怎么做!
1. 单纯的绘图画出来,包括星星。
2. 用UI给的图片来做~。

这么看肯定是用图片来做最简单了,因为存在半颗星的情况所以需要三张图片资源,选中的图片,空星的图片,半星的图片。

1. 创建自定义View

public class MyRatingView extends View {


}

2. 思考下有哪些属性并初始化


public class MyRatingView extends View { Paint paint; Bitmap bitNight; Bitmap bitHalf; Bitmap bitDark; //存放每个星星的位置 List<RectF> rectFs = new ArrayList<>(); //存的是类型标记 List<RatingType> bitMark = new ArrayList<>(); //最大星星数量 int starNum = 5; //当前打分的分数 单位是半颗 int ratingNum = 3; //间距 int colSpacing = 30; //星星图标的宽高 int[] starWidthHeight; public MyRatingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); initStar(); } private void init() { paint = new Paint(); paint.setAntiAlias(true); bitNight = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rating_star_selected); bitDark = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rating_star_default); bitHalf = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rating_star_half); starWidthHeight = new int[]{bitNight.getWidth(), bitNight.getHeight()}; } //计算星星 private void initStar() { //先清除 rectFs.clear(); bitMark.clear(); //是否有半星 boolean hashHalf = ratingNum % 2 > 0; //多少个整星 int nightNum = ratingNum / 2; //暗的星 int darkNum = starNum - nightNum - (hashHalf ? 1 : 0); for (int i = 0; i < nightNum; i++) { bitMark.add(RatingType.Night); } if (hashHalf) bitMark.add(RatingType.Half); for (int i = 0; i < darkNum; i++) { bitMark.add(RatingType.Dark); } } private enum RatingType { Night, Dark, Half } }
  • 该有的变量都有了,这里就不做细节的操作了,因为所有的自定义控件都是写完后慢慢完善的,包括xml自定义属性。

3. 接着考虑点击事件

    float lastMoveX, lastMoveY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:

                //getParent().requestDisallowInterceptTouchEvent(true);
                //按下获取位置
                float clickX = lastMoveX = event.getX();
                float clickY = lastMoveY = -event.getY();

                Log.i("MyRatingView", "click X:" + clickX + " Y:" + clickY);
                //实际有内容的宽度
                //int actualWidth = (starNum * starWidthHeight[0]) + (starNum - 1) * colSpacing;
                int position = -1;
                int direction = 0;

                //检测点击的坐标是在哪个星星上
                int[] pd = findClickItem(clickX, clickY);
                position = pd[0];
                direction = pd[1];

                if (position >= 0) {

                    if (direction == 1) {

                        ratingNum = (position + 1) * 2;

                    } else if (direction == -1) {

                        if (position > 0) {
                            ratingNum = (position + 1) * 2 - 1;
                        } else {
                            ratingNum = 1;
                        }
                    }

                    //重新初始化数据
                    initStar();
                    //重新绘制UI
                    invalidate();
                }

                break;

        }

        return super.onTouchEvent(event);
    }

    private int[] findClickItem(float clickX, float clickY) {

        int position = -1;
        int direction = 0;

        float left, right, top, bottom;

        //rectFs 的数据是第一次onDraw绘制的时候保存的星星的位置
        for (int i = 0; i < rectFs.size(); i++) {

            left = rectFs.get(i).left;
            right = rectFs.get(i).right;
            top = rectFs.get(i).top;
            bottom = rectFs.get(i).bottom;

            //判断第几个星星上
            if (clickX >= left && clickX <= right && clickY <= top && clickY >= bottom) {
                position = i;
                //判断点击了左边还是星星右边
                if (clickX >= left + starWidthHeight[0] / 2) {
                    //右边
                    direction = 1;
                } else {
                    //左边
                    direction = -1;
                }

                break;
            }

        }
        return new int[]{position, direction};
    }

– 上面的点击事件流程就是 先判断点击的点是否在星星上,在判断点击了左边还是右边。

4. onDraw 开始绘制界面


float left,top,right,bottom; @Override protected void onDraw(Canvas canvas) { //防止多次绘制出现重复数据 rectFs.clear(); int i = 0; for (RatingType ratingType : bitMark) { left = i * starWidthHeight[0] + (i == 0 ? 0 : (i * colSpacing)); top = getY(); right = left + starWidthHeight[0]; bottom = top - starWidthHeight[1]; rectFs.add(new RectF(left, top, right, bottom)); if (ratingType == RatingType.Night) { canvas.drawBitmap(bitNight, left, top, paint); } else if (ratingType == RatingType.Half) { canvas.drawBitmap(bitHalf, left, top, paint); } else if (ratingType == RatingType.Dark) { canvas.drawBitmap(bitDark, left, top, paint); } i++; } super.onDraw(canvas); }

**5. onMeasure **

这个时候还只能给 match_parent 或者固定宽高,所以我们处理下 wrap_content 。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec);

        int width, height = 0;

        if (AT_MOST == widthMode) {
            Log.i("MyRatingView", "AT_MOST");
            width = starNum * starWidthHeight[0] + (starNum - 1) * colSpacing;

        } else {
            width = widthMeasureSize;
        }

        if (AT_MOST == heightMode) {
            height = starWidthHeight[1];
        } else {
            //
            height = starWidthHeight[1];
        }


        setMeasuredDimension(width, height);
    }

6.大功告成了,最后贴上完整的代码


import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import com.tangdunguanjia.o2o.R; import java.util.ArrayList; import java.util.List; import static android.view.View.MeasureSpec.AT_MOST; /** * Created by Administrator on 2018/6/8. * 自定义星星View */ public class MyRatingView extends View { Paint paint; Bitmap bitNight; Bitmap bitHalf; Bitmap bitDark; volatile List<RectF> rectFs = new ArrayList<>(); //存的是类型标记 List<RatingType> bitMark = new ArrayList<>(); //最大星星数量 int starNum = 5; //当前打分的分数 单位是半颗 int ratingNum = 3; //间距 int colSpacing = 30; //星星图标的宽高 int[] starWidthHeight; public MyRatingView(Context context) { this(context, null); } public MyRatingView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, -1); } public MyRatingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); initStar(); } private void init() { paint = new Paint(); paint.setAntiAlias(true); bitNight = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rating_star_selected); bitDark = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rating_star_default); bitHalf = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rating_star_half); starWidthHeight = new int[]{bitNight.getWidth(), bitNight.getHeight()}; } //计算星星 private void initStar() { //先清除 rectFs.clear(); bitMark.clear(); //是否有半星 boolean hashHalf = ratingNum % 2 > 0; //多少个整星 int nightNum = ratingNum / 2; //暗的星 int darkNum = starNum - nightNum - (hashHalf ? 1 : 0); for (int i = 0; i < nightNum; i++) { bitMark.add(RatingType.Night); } if (hashHalf) bitMark.add(RatingType.Half); for (int i = 0; i < darkNum; i++) { bitMark.add(RatingType.Dark); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec); int width, height = 0; if (AT_MOST == widthMode) { Log.i("MyRatingView", "AT_MOST"); width = starNum * starWidthHeight[0] + (starNum - 1) * colSpacing; } else { width = widthMeasureSize; } if (AT_MOST == heightMode) { height = starWidthHeight[1]; } else { // height = starWidthHeight[1]; } setMeasuredDimension(width, height); } float lastMoveX, lastMoveY; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //getParent().requestDisallowInterceptTouchEvent(true); //按下获取位置 float clickX = lastMoveX = event.getX(); float clickY = lastMoveY = -event.getY(); Log.i("MyRatingView", "click X:" + clickX + " Y:" + clickY); //实际有内容的宽度 //int actualWidth = (starNum * starWidthHeight[0]) + (starNum - 1) * colSpacing; int position = -1; int direction = 0; int[] pd = findClickItem(clickX, clickY); position = pd[0]; direction = pd[1]; if (position >= 0) { if (direction == 1) { ratingNum = (position + 1) * 2; } else if (direction == -1) { if (position > 0) { ratingNum = (position + 1) * 2 - 1; } else { ratingNum = 1; } } initStar(); invalidate(); } break; } return super.onTouchEvent(event); } private int[] findClickItem(float clickX, float clickY) { int position = -1; int direction = 0; float left, right, top, bottom; for (int i = 0; i < rectFs.size(); i++) { left = rectFs.get(i).left; right = rectFs.get(i).right; top = rectFs.get(i).top; bottom = rectFs.get(i).bottom; //判断第几个星星上 if (clickX >= left && clickX <= right && clickY <= top && clickY >= bottom) { position = i; //判断点击了左边还是星星右边 if (clickX >= left + starWidthHeight[0] / 2) { //右边 direction = 1; } else { //左边 direction = -1; } break; } } return new int[]{position, direction}; } float left,top,right,bottom; @Override protected void onDraw(Canvas canvas) { rectFs.clear(); int i = 0; for (RatingType ratingType : bitMark) { left = getX() + i * starWidthHeight[0] + (i == 0 ? 0 : (i * colSpacing)); top = getY(); right = left + starWidthHeight[0]; bottom = top - starWidthHeight[1]; rectFs.add(new RectF(left, top, right, bottom)); if (ratingType == RatingType.Night) { canvas.drawBitmap(bitNight, left, top, paint); } else if (ratingType == RatingType.Half) { canvas.drawBitmap(bitHalf, left, top, paint); } else if (ratingType == RatingType.Dark) { canvas.drawBitmap(bitDark, left, top, paint); } i++; } super.onDraw(canvas); } private enum RatingType { Night, Dark, Half } }

上一篇

发表评论