自定義View——點贊控件

預覽效果

目錄在這裏插入圖片描述

  • 圖片類:LikeImageView
  • 文字類:LikeCharTextView
  • 整合類:LikeView.java
  • 自定義屬性:attrs.xml

代碼

  • LikeCharTextView

public class LikeCharTextView extends View {

    public static final int DEFAULT_TEXTCOLOR = Color.BLACK;
    public static final int DEFAULT_TEXTSIZE  = 36;

    private TextPaint       newTextPaint, oldTextPaint;

    private AnimatorSet     addAnimator;
    private AnimatorSet     minusAnimator;

    private int             measureWidth;
    private int             measureHeight;

    private int             textColor         = DEFAULT_TEXTCOLOR;
    private int             textSize          = DEFAULT_TEXTSIZE;

    private int             num;
    private int             oldNum;
    private int             newNum;

    private int             animatorOldY;
    private float           animatorOldAlpha  = 1;

    private int             animatorNewY;
    private float           animatorNewAlpha  = 0;
    private int             baseline;

    public LikeCharTextView(Context context) {
        super(context);

        init();
    }

    public LikeCharTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        initAttr(context, attrs);

        init();
    }

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

        initAttr(context, attrs);

        init();
    }

    /**
     * 初始化屬性
     *
     * @param context
     * @param attrs
     */
    private void initAttr(Context context, @Nullable AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.LikeCharTextView);
        textColor = typedArray.getColor(R.styleable.LikeCharTextView_textColor,
                DEFAULT_TEXTCOLOR);
        textSize = typedArray.getDimensionPixelSize(R.styleable.LikeCharTextView_textSize,
                DEFAULT_TEXTSIZE);
        num = typedArray.getInt(R.styleable.LikeCharTextView_number, 0);
        if (0 > num || num > 10) {
            throw new IllegalArgumentException("Number is only 0-9");
        }
        oldNum = num;

        typedArray.recycle();
    }

    /**
     * 初始化
     */
    private void init() {
        initPaints();
        initParams();
    }

    /**
     * 初始化畫筆
     */
    private void initPaints() {
        newTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        newTextPaint.setStyle(Paint.Style.FILL);
        newTextPaint.setTextSize(textSize);
        newTextPaint.setColor(textColor);
        newTextPaint.setTextAlign(Paint.Align.CENTER);

        oldTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        oldTextPaint.set(newTextPaint);
    }

    /**
     * 初始化參數
     */
    private void initParams() {
        Paint.FontMetrics fontMetrics = newTextPaint.getFontMetrics();
        measureWidth = (int) newTextPaint.measureText(String.valueOf(num));
        measureHeight = (int) (fontMetrics.bottom - fontMetrics.top);

        float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        baseline = (int) (measureHeight * 1.0f / 2 + distance);

        animatorOldY = baseline;
    }

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

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.UNSPECIFIED:
                break;
            case MeasureSpec.AT_MOST:
                widthSize = measureWidth;
                break;
            case MeasureSpec.EXACTLY:
                break;
        }

        int heightMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(widthMeasureSpec);
        switch (heightMode) {
            case MeasureSpec.UNSPECIFIED:
                break;
            case MeasureSpec.AT_MOST:
                heightSize = measureHeight;
                break;
            case MeasureSpec.EXACTLY:
                break;
        }

        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        oldTextPaint.setAlpha((int) (255 * animatorOldAlpha));
        canvas.drawText(String.valueOf(oldNum), width / 2, animatorOldY, oldTextPaint);

        newTextPaint.setAlpha((int) (255 * animatorNewAlpha));
        canvas.drawText(String.valueOf(newNum), width / 2, animatorNewY, newTextPaint);
    }

    public void setTextColor(int textColor) {
        this.textColor = textColor;
        init();
        invalidate();
    }

    public void setTextSize(int textSize) {
        this.textSize = textSize;
        init();
        invalidate();
    }

    public void setAnimatorOldY(int animatorOldY) {
        this.animatorOldY = animatorOldY;
        invalidate();
    }

    public void setAnimatorOldAlpha(float animatorOldAlpha) {
        this.animatorOldAlpha = animatorOldAlpha;
        invalidate();
    }

    public void setAnimatorNewY(int animatorNewY) {
        this.animatorNewY = animatorNewY;
        invalidate();
    }

    public void setAnimatorNewAlpha(float animatorNewAlpha) {
        this.animatorNewAlpha = animatorNewAlpha;
        invalidate();
    }

    public void setNum(int num) {
        this.num = num;
        if (0 > num || num > 10) {
            throw new IllegalArgumentException("Number is only 0-9");
        }
        oldNum = num;
        invalidate();
    }

    public void add() {
        Logger.e("執行加動畫.基線:" + baseline);
        ObjectAnimator oldYAnimator = ObjectAnimator.ofInt(this, "animatorOldY", baseline, 0);
        ObjectAnimator oldAlphaAnimator = ObjectAnimator.ofFloat(this, "animatorOldAlpha", 1, 0);
        ObjectAnimator newYAnimator = ObjectAnimator.ofInt(this, "animatorNewY", baseline * 2,
                baseline);
        ObjectAnimator newAlphaAnimator = ObjectAnimator.ofFloat(this, "animatorNewAlpha", 0, 1);

        addAnimator = new AnimatorSet();
        addAnimator.playTogether(oldYAnimator, oldAlphaAnimator, newYAnimator, newAlphaAnimator);
        addAnimator.setInterpolator(new LinearInterpolator());
        addAnimator.setDuration(300);
        addAnimator.start();
    }

    public void minus() {
        Logger.e("執行減動畫.基線:" + baseline);
        ObjectAnimator oldYAnimator = ObjectAnimator.ofInt(this, "animatorOldY", baseline,
                baseline * 2);
        ObjectAnimator oldAlphaAnimator = ObjectAnimator.ofFloat(this, "animatorOldAlpha", 1, 0);
        ObjectAnimator newYAnimator = ObjectAnimator.ofInt(this, "animatorNewY", 0, baseline);
        ObjectAnimator newAlphaAnimator = ObjectAnimator.ofFloat(this, "animatorNewAlpha", 0, 1);

        minusAnimator = new AnimatorSet();
        minusAnimator.playTogether(oldYAnimator, oldAlphaAnimator, newYAnimator, newAlphaAnimator);
        minusAnimator.setInterpolator(new LinearInterpolator());
        minusAnimator.setDuration(300);
        minusAnimator.start();
    }

    public void change(boolean isAdd) {
        Logger.e("charTextVie.點擊事件:" + isAdd);
        if (isAdd) {
            if (null != addAnimator && addAnimator.isStarted()) {
                Logger.e("charTextVie.加動畫已執行.取消");
                addAnimator.cancel();
            }
            if (null != minusAnimator && minusAnimator.isStarted()) {
                Logger.e("charTextVie.減動畫已執行.取消");
                minusAnimator.cancel();
            }
            sumNum(false);
            minus();
        } else {
            if (null != minusAnimator && minusAnimator.isStarted()) {
                Logger.e("charTextVie.減動畫已執行.取消");
                minusAnimator.cancel();
            }
            if (null != addAnimator && addAnimator.isStarted()) {
                Logger.e("charTextVie.加動畫已執行.取消");
                addAnimator.cancel();
            }
            sumNum(true);
            add();
        }
    }

    /**
     * 重新計算繪畫的值
     *
     * @param isAdd
     */
    private void sumNum(boolean isAdd) {
        Logger.e("計算值開始");
        oldNum = num;
        newNum = num + (isAdd ? 1 : -1);
        if (newNum < 0) {
            newNum = 9;
        } else if (newNum > 9) {
            newNum = 0;
        }
        num = newNum;
        Logger.e("計算值結束:" + num);
    }
}
  • LikeImageView

public class LikeImageView extends View {

    private Paint imagePaint, shiningPaint;

    private int shiningMoveX;
    private int shiningMoveY;

    private int measureWidth;
    private int measureHeight;
    private Bitmap selectedBtimap;
    private Bitmap selectedShiningBtimap;
    private Bitmap unSelectedBtimap;

    private boolean isAdd = false;

    private float shiningAlpha = isAdd ? 1f : 0f;

    public LikeImageView(Context context) {
        super(context);

        init();
    }

    public LikeImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        init();
    }

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

        init();
    }

    private void init() {
        imagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        shiningPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        selectedBtimap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_like_selected);
        selectedShiningBtimap = BitmapFactory.decodeResource(getResources(),
                R.mipmap.ic_like_selected_shining);
        unSelectedBtimap = BitmapFactory.decodeResource(getResources(),
                R.mipmap.ic_like_unselected);
    }

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

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.UNSPECIFIED:
                break;
            case MeasureSpec.AT_MOST:
                widthSize = Math.max(selectedBtimap.getWidth(), unSelectedBtimap.getWidth());
                shiningMoveX = (int) (widthSize * 1.0f - selectedShiningBtimap.getWidth()) - 2;
                break;
            case MeasureSpec.EXACTLY:
                break;
        }

        int heightMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(widthMeasureSpec);
        switch (heightMode) {
            case MeasureSpec.UNSPECIFIED:
                break;
            case MeasureSpec.AT_MOST:
                heightSize = Math.max(selectedBtimap.getHeight(), unSelectedBtimap.getHeight());
                shiningMoveY = (int) (selectedShiningBtimap.getHeight() * 1.0f / 3);
                heightSize += shiningMoveY;
                break;
            case MeasureSpec.EXACTLY:
                break;
        }

        setMeasuredDimension(widthSize, heightSize);
    }

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

        int width = getWidth();
        int height = getHeight();
        Rect src = new Rect(0, 0, width, height);
        Rect selectDst = new Rect(0, shiningMoveY, selectedBtimap.getWidth(), height);
        if (isAdd) {
            //畫紅贊
            canvas.drawBitmap(selectedBtimap, src, selectDst, imagePaint);
            //畫陰影
            shiningPaint.setAlpha((int) (255 * shiningAlpha));
            Rect shiningDst = new Rect(shiningMoveX, 0,
                    shiningMoveX + selectedShiningBtimap.getWidth(), selectedShiningBtimap.getHeight());
            canvas.drawBitmap(selectedShiningBtimap, src, shiningDst, shiningPaint);
        } else {
            //畫灰贊
            canvas.drawBitmap(unSelectedBtimap, src, selectDst, imagePaint);
        }
    }

    public void setShiningAlpha(float shiningAlpha) {
        this.shiningAlpha = shiningAlpha;
        invalidate();
    }

    public void setAdd(boolean add) {
        isAdd = add;
        shiningAlpha = 1.0f;
        invalidate();
    }

    public void changeLike(boolean isAdd) {
        this.isAdd = !isAdd;
        invalidate();
        anim();
    }

    private void anim() {
        ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(this, "scaleX", 0.7f, 1f);
        scaleXAnim.setDuration(500);
        ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(this, "scaleY", 0.7f, 1f);
        scaleYAnim.setDuration(500);
        ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, "shiningAlpha", 0f, 1f);
        alphaAnim.setDuration(500);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(scaleXAnim, scaleYAnim, alphaAnim);
        animatorSet.setInterpolator(new BounceInterpolator());
        animatorSet.start();
    }
}
  • LikeView

public class LikeView extends LinearLayout {
    private final int IMAGEPADDING = 4;

    private boolean isAdd = false;

    private int num;
    private int textSize;
    private int textColor;

    private int imagePadding;

    private List<LikeCharTextView> charTvs = new ArrayList<>();
    private LikeImageView likeImageView;

    public LikeView(Context context) {
        super(context);
        init();
    }

    public LikeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        initAttr(context, attrs);

        init();
    }

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

        initAttr(context, attrs);

        init();
    }

    private void initAttr(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LikeView);
        textColor = typedArray.getColor(R.styleable.LikeView_textColor,
                LikeCharTextView.DEFAULT_TEXTCOLOR);
        textSize = typedArray.getDimensionPixelSize(R.styleable.LikeView_textSize,
                LikeCharTextView.DEFAULT_TEXTSIZE);
        num = typedArray.getInt(R.styleable.LikeView_number, 0);
        imagePadding = typedArray.getDimensionPixelSize(R.styleable.LikeView_imagePadding, IMAGEPADDING);

        typedArray.recycle();
    }

    /**
     * 初始化
     */
    private void init() {

        initView();
    }

    protected void initView() {
        removeAllViews();

        likeImageView = new LikeImageView(getContext());
        likeImageView.setAdd(isAdd);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.rightMargin = imagePadding;
        likeImageView.setLayoutParams(layoutParams);
        addView(likeImageView);

        charTvs.clear();
        String str_num = String.valueOf(num);
        for (int i = 0; i < str_num.length(); i++) {
            LikeCharTextView textView = new LikeCharTextView(getContext());
            int show_num = Integer.valueOf(str_num.substring(i, i + 1));
            Log.e("zanview", "show_num:" + show_num);
            textView.setTextSize(textSize);
            textView.setTextColor(textColor);
            textView.setNum(show_num);
            addView(textView);
            charTvs.add(textView);
        }
    }

    public void setNum(int num) {
        this.num = num;
        init();
        invalidate();
    }

    public void setAdd(boolean add) {
        isAdd = add;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //計算出所有的childView的寬高
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    /**
     * 測量寬度
     *
     * @param widthMeasureSpec
     * @return
     */
    private int measureWidth(int widthMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.UNSPECIFIED:
                break;
            case MeasureSpec.AT_MOST:
                widthSize = 0;
                for (int i = 0; i < getChildCount(); i++) {
                    View childView = getChildAt(i);
                    //獲取子view的寬
                    int cWidth = childView.getMeasuredWidth();
                    MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
                    widthSize += cWidth + params.leftMargin + params.rightMargin;
                }
                break;
            case MeasureSpec.EXACTLY:
                break;
        }
        return widthSize;
    }

    /**
     * 測量高度
     *
     * @param widthMeasureSpec
     * @return
     */
    private int measureHeight(int widthMeasureSpec) {
        int heightMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(widthMeasureSpec);
        switch (heightMode) {
            case MeasureSpec.UNSPECIFIED:
                break;
            case MeasureSpec.AT_MOST:
                heightSize = 0;
                for (int i = 0; i < getChildCount(); i++) {
                    View childView = getChildAt(i);
                    //獲取子view的寬
                    int cWidth = childView.getMeasuredHeight();
                    MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
                    int height = cWidth + params.leftMargin + params.rightMargin;
                    heightSize = Math.max(heightSize, height);
                }
                break;
            case MeasureSpec.EXACTLY:
                break;
        }
        return heightSize;
    }

    private boolean click = false;

    private final int MOHUFANWEI = 10;

    private float lastX = 0;
    private float lastY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (1 == event.getPointerCount()) {
                    click = true;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (click) {
                    onClick();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(lastX - x) > MOHUFANWEI || Math.abs(lastY - y) > MOHUFANWEI) {
                    click = false;
                }
                break;
        }

        lastX = x;
        lastY = y;
        return true;
    }

    private void onClick() {
        Logger.e("點擊事件" + isAdd);
        String str_num = String.valueOf(num);
        Logger.e("點擊事件,str_num:" + str_num);
        boolean nextAnim = false;
        if (isAdd) {
            likeImageView.changeLike(true);
            for (int i = (str_num.length() - 1); i >= 0; i--) {
                int chr_num = Integer.valueOf(str_num.substring(i, i + 1));
                Logger.e("點擊事件,chr_num:%d,charTvs.size:%d,i:%d", chr_num, charTvs.size(), i);
                Logger.e("是否執行動畫:" + (charTvs.size() > i));
                if (charTvs.size() > i) {
                    if (i == (str_num.length() - 1) || nextAnim) {
                        Logger.e("點擊事件,執行個位動畫||%b執行執行上%d位動畫", nextAnim, i);
                        charTvs.get(i).change(true);

                        chr_num--;
                        Logger.e("chr_num:%d,是否執行上一位動畫:", chr_num, (chr_num < 0));
                        if (chr_num < 0) {
                            nextAnim = true;
                        } else {
                            nextAnim = false;
                        }

                        Logger.e("nextAnim:" + nextAnim);
                    }
                }
            }
            num--;
            isAdd = !isAdd;
        } else {
            likeImageView.changeLike(false);
            for (int i = (str_num.length() - 1); i >= 0; i--) {
                int chr_num = Integer.valueOf(str_num.substring(i, i + 1));
                Logger.e("點擊事件,chr_num:%d,charTvs.size:%d,i:%d", chr_num, charTvs.size(), i);
                Logger.e("是否執行動畫:" + (charTvs.size() > i));
                if (charTvs.size() > i) {
                    if (i == (str_num.length() - 1) || nextAnim) {
                        Logger.e("點擊事件,執行個位動畫||%b執行執行上%d位動畫", nextAnim, i);
                        charTvs.get(i).change(false);

                        chr_num++;
                        Logger.e("chr_num:%d,是否執行上一位動畫:", chr_num, (chr_num > 9));
                        if (chr_num > 9) {
                            nextAnim = true;
                        } else {
                            nextAnim = false;
                        }
                        Logger.e("nextAnim:" + nextAnim);
                    }
                }
            }
            num++;
            isAdd = !isAdd;
        }
    }
}
  • attrs.xml

    <attr name="textSize" format="dimension" />
    <attr name="textColor" format="color" />
    <attr name="number" format="integer" />

    <attr name="imageWidth" format="dimension" />
    <attr name="imageHeight" format="dimension" />

    <declare-styleable name="LikeView">
        <attr name="textSize" />
        <attr name="textColor" />
        <attr name="number" />
        <attr name="imageWithd" />
        <attr name="imageHeight" />
        <attr name="imagePadding" format="dimension" />
    </declare-styleable>

    <declare-styleable name="LikeCharTextView">
        <attr name="textSize" />
        <attr name="textColor" />
        <attr name="number" />
    </declare-styleable>

    <declare-styleable name="LikeImageView">
        <attr name="imageWithd" />
        <attr name="imageHeight" />
    </declare-styleable>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章