• Android自定义View(二)


    前言

    魅族手机的闹钟应用中有个倒计时,这个控件还是蛮有趣的。左边是魅族闹钟,右边是我们最终实现的效果,虽然有些细节还需优化,不过基本上已经达到了想要的效果,我们先来就来看看如何实现吧。
    魅族闹钟 最终效果

    分析

    确定宽高

    对一个Android自定义控件来说,一般都经过三个步骤

    • onLayout()

    • onMeasure()

    • onDraw()

    onLayout明确子控件在父控件中的位置(本控件不需要重写),onMeasure是确定控件的大小(宽、高),而onDraw是我们重点关注的方法,我们需要在这个方法中写入显示View的逻辑代码。
    对于本控件,控件的高度 应该等于细线的高度(mLineHeight)加上数字的高度(mFontHeight),当然为了好看,中间需要设上一些边距(mPadding),因此本控件的高度应该为 mFontHeight + mLineHeight + 10 + mPadding,测量代码如下

        private int measureHeight(int heightMeasureSpec) {
            int mode = MeasureSpec.getMode(heightMeasureSpec);
            int size = MeasureSpec.getSize(heightMeasureSpec);
            switch (mode) {
                case MeasureSpec.EXACTLY:
                    return size ;
                case MeasureSpec.AT_MOST:
                    return Math.min(size, mFontHeight + mLineHeight + 10 + mPadding) ;
            }
            return size ;
        }
    

    同样地,控件的宽度其实就是0~1000的间隔,测量代码如下

        private int measureWidth(int widthMeasureSpec) {
            int mode = MeasureSpec.getMode(widthMeasureSpec);
            int size = MeasureSpec.getSize(widthMeasureSpec);
            switch (mode) {
                case MeasureSpec.EXACTLY:
                    return size ;
                case MeasureSpec.AT_MOST:
                    int result = getPaddingLeft() + mContentWidth + getPaddingRight() ;
                    return Math.min(size, result) ;
            }
            return size ;
        }
    

    画刻度尺

    重点在于刻度尺的计算。思路是先draw上头的数字,然后再draw下边的线条,判断位置确定是否需要draw上头的数字即可。其实就是坐标的计算。代码如下

            int startX = mPadding;
            int stopX = mPadding;
            int stopY =mHeight - mPadding;
            for (int i = 0 ; i<=mContentWidth ; i += mFontWidth)
            if (i % (mFontWidth *10) == 0) {
                canvas.drawLine(startX + i, mFontHeight + mPadding + 5 , stopX + i, stopY, mTextPaint);
                canvas.drawText(i + "", startX + i, mFontHeight + mPadding, mTextPaint);
            } else if (i % mFontWidth == 0) {
                canvas.drawLine(startX + i, mFontHeight + mPadding + 10, stopX + i, stopY, mPaint);
            }
    

    让View动起来

    Android本身提供了移动View的API,因此让View动起来也是不难的。两种思路

    • 监听Touch事件,当Touch坐标变化时,计算坐标位置,不断调用scrollTo(x,0)达到变换坐标的目的

    • 监听Touch事件,记录上次横坐标和本次横坐标的差值,然后调用scrollBy(delta, 0) 即可移动

    其实两种方法本质上都是一样的。ScrollBy其实也是调用了scrollTo方法。本文采用方法二。其代码如下

    @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastX = (int) event.getX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int x = (int) event.getX();
                    int deltaX = x - mLastX;
                    scrollBy(-deltaX, 0);
                    mLastX = x;
                    break;
            }
            return true;
        }
    

    当然了,我们是不能让View无限移动的,因此需要重写scrollBy方法,限制View不能超过边界。
    代码如下

        @Override
        public void scrollBy(int x, int y) {
            super.scrollBy(x, y);
            if (x < 0) { // drag to left
                if (getScrollX() < -getCenter() + mPadding) {
                    scrollTo(-getCenter() + mPadding, 0);
                }
            } else
            if (x >0) { // drag to right
               if (mContentWidth - getScrollX() + x < getCenter()) {
                    scrollTo(mContentWidth - getCenter() + mFontWidth, 0);
               }
            }
        }
    

    当超过边界时,直接调用scrollTo,让View停留在特定的位置即可。需要注意的一点是,View往左滑动时,ScrollX的值是负的。

    完整代码

    package com.nancyyihao.demo;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Scroller;
    
    public class RulerView extends View {
    
        private static final String TAG = RulerView.class.getSimpleName() ;
        private TextPaint mTextPaint;
        private Paint mPaint ;
    
        private int mWidth;
        private int mHeight;
        private int mPadding = 10;
        private Scroller mScroller  ;
        private int mLastX;
        private int mContentWidth = 1000;
        private int mLineHeight = 50;
        private int mFontHeight;
        private int mFontWidth = 10;
    
        private onValueChangedListener mValueChangedListener;
    
        public interface onValueChangedListener {
            void onValueChanged(int newValue);
        }
    
        public RulerView(Context context) {
            super(context);
            init(context);
        }
    
        public RulerView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public RulerView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(context);
        }
    
        public void setOnValueChangedListener(onValueChangedListener listener) {
            this.mValueChangedListener = listener ;
        }
    
        private void init(Context context) {
            mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
            mTextPaint.setTextAlign(Paint.Align.CENTER);
            mTextPaint.setColor(Color.parseColor("#FF4081"));
            mTextPaint.setTextSize(30);
            mTextPaint.setStrokeWidth(2f);
            Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
            mFontHeight = Math.round(Math.abs(fontMetrics.top) + Math.abs(fontMetrics.bottom)) ;
    
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
            mPaint.setColor(Color.DKGRAY);
            mPaint.setStrokeWidth(2f);
            mPaint.setTextSize(30);
            mPaint.setTextAlign(Paint.Align.CENTER);
    
            //mScroller = new Scroller(context) ;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            mWidth = measureWidth(widthMeasureSpec);
            mHeight = measureHeight(heightMeasureSpec);
            setMeasuredDimension(mWidth , mHeight );
        }
    
        private int measureWidth(int widthMeasureSpec) {
            int mode = MeasureSpec.getMode(widthMeasureSpec);
            int size = MeasureSpec.getSize(widthMeasureSpec);
            switch (mode) {
                case MeasureSpec.EXACTLY:
                    return size ;
                case MeasureSpec.AT_MOST:
                    int result = getPaddingLeft() + mContentWidth + getPaddingRight() ;
                    return Math.min(size, result) ;
            }
            return size ;
        }
    
        private int measureHeight(int heightMeasureSpec) {
            int mode = MeasureSpec.getMode(heightMeasureSpec);
            int size = MeasureSpec.getSize(heightMeasureSpec);
            switch (mode) {
                case MeasureSpec.EXACTLY:
                    return size ;
                case MeasureSpec.AT_MOST:
                    return Math.min(size, mFontHeight + mLineHeight + 10 + mPadding) ;
            }
            return size ;
        }
    
    //    private void smoothScrollTo(int destX, int destY) {
    //        int scrollX = getScrollX() ;
    //        int delta = destX - scrollX ;
    //        mScroller.startScroll(scrollX, 0, delta, 0 , 1000);
    //        invalidate();
    //    }
    //
    //    @Override
    //    public void computeScroll() {
    //        super.computeScroll();
    //        if (mScroller.computeScrollOffset()) {
    //            smoothScrollTo(mScroller.getCurrX(), mScroller.getCurrY());
    //            postInvalidate();
    ////            ((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
    ////            invalidate();
    //        }
    //    }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int startX = mPadding;
            int stopX = mPadding;
            int stopY =mHeight - mPadding;
            for (int i = 0 ; i<=mContentWidth ; i += mFontWidth)
            if (i % (mFontWidth *10) == 0) {
                canvas.drawLine(startX + i, mFontHeight + mPadding + 5 , stopX + i, stopY, mTextPaint);
                canvas.drawText(i + "", startX + i, mFontHeight + mPadding, mTextPaint);
            } else if (i % mFontWidth == 0) {
                canvas.drawLine(startX + i, mFontHeight + mPadding + 10, stopX + i, stopY, mPaint);
            }
        }
    
    
        private int calcValue() {
            return  ( getCenter() + getScrollX() - mPadding) ; //minus startX
        }
    
        private int getCenter() {
            return (getRight() - getLeft()) / 2 ;
        }
    
        @Override
        public void scrollBy(int x, int y) {
            super.scrollBy(x, y);
            if (x < 0) { // drag to left
                if (getScrollX() < -getCenter() + mPadding) {
                    scrollTo(-getCenter() + mPadding, 0);
                }
            } else
            if (x >0) { // drag to right
               if (mContentWidth - getScrollX() + x < getCenter()) {
                    scrollTo(mContentWidth - getCenter() + mFontWidth, 0);
               }
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastX = (int) event.getX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int x = (int) event.getX();
                    int deltaX = x - mLastX;
                    scrollBy(-deltaX, 0);
                    mLastX = x;
                    if (mValueChangedListener != null)
                        mValueChangedListener.onValueChanged(calcValue());
                    break;
            }
            return true;
        }
    }
    

    总结

    整体上还是比较粗糙,原形虽然有了,但是还需要优化。

    参考

    【Android】自定义View —— 滑动的次数选择器
    android 滚轮刻度尺的实现
    Android View自定义专题二(View滑动的实现)

  • 相关阅读:
    git
    Java命令行参数解析
    Java调用本地命令
    理解JavaScript继承
    python selenium自动化(三)Chrome Webdriver的兼容
    python selenium自动化(二)自动化注册流程
    python selenium自动化(一)点击页面链接测试
    使用python selenium进行自动化functional test
    JUnit中测试异常抛出的方法
    爬坑 http协议的options请求
  • 原文地址:https://www.cnblogs.com/jasonkent27/p/5836071.html
Copyright © 2020-2023  润新知