在前面一篇博客中介绍了Android中水波纹的使用,如果忘记了可以去复习一下Android中水波纹使用,但是那种实现方法要在API21+以上才能使用。如果我们要兼容API21以下的系统,则需要通过第三方的类库,即通过自定义视图来实现水波纹的效果。

自定义视图,是Android中扩展控件常用的方法,这里不做过多的介绍,如果不太会,可以去搜索学习一下。这里主要介绍如何实现水波纹效果,其次是对自定义视图中常用的问题总结一下。

自定义视图会涉及到构造函数,现在的自定义视图构造函数有4个,最后一个一般是API21+,所以平时不常用。对这个不了解的可以去看看Android View 四个构造函数详解。

总结一下:
一般第一个构造函数是指在代码中创建实例化调用;第二个构造函数是通过XML方式建立控件视图,提供AttributeSet属性设置;第三个也是通过XML方式建立控件视图,提供AttributeSet属性设置,同时提供默认样式值defStyleAttr;第四个一样通过XML方式创建,除了提供跟第三个一样的参数外,新增defStyleRes。

View类的后两个构造函数都是与主题相关的。
它们的属性赋值优先级为:

XML直接定义 > XML中style引用 > defStyleAttr > defStyleRes > theme直接定义

了解了上面后,我们再来看看几个自定义视图中会常用到的函数:
onFinishInflate()
onAttachedToWindow()
onMeasure()
onSizeChanged ()
onLayout ()
onConfigurationChanged()
onDraw()
dispatchDraw ()
draw()
onTouchEvent()
onInterceptTouchEvent()

onFinishInflate()方法一般是在xml文件加载完成后调用这个方法;
onAttachedToWindow()方法是将视图依附到Window中;
onMeasure()方法是测量自定义视图的大小 ;
onSizeChanged()方法是自定义视图大小发生改变时调用;
onLayout()方法是将自定义视图放置到父容器的具体某个位置中;
onConfigurationChanged()方法是在当手机屏幕从横屏和竖屏相互转化时调用;
onDraw() 、dispatchDraw ()、draw()这三个方法,则是根据具体的情况来调用的;

  1. 自定义一个view时,重写onDraw()。
    调用view.invalidate(),会导致draw流程重新执行。
    view.postInvalidate(); //是在非UI线程上调用的

  2. 自定义一个ViewGroup,重写onDraw()。
    onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)。
    表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。
    因此,一般直接重写dispatchDraw()来绘制viewGroup

  3. dispatchDraw会()对子视图进行分发绘制操作。

总结一下:
View组件的绘制会调用draw(Canvas canvas)方法,draw过程中主要是先画Drawable背景,对 drawable调用setBounds()然后是draw(Canvas c)方法。有点注意的是背景drawable的实际大小会影响view组件的大小,drawable的实际大小通过getIntrinsicWidth()和getIntrinsicHeight()获取,当背景比较大时view组件大小等于背景drawable的大小。

画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth()。

该总结来自网络,感谢分享~~~

最后是onTouchEvent()和onInterceptTouchEvent()方法,这两个方法也是我们经常会用到的。

onInterceptTouchEvent()方法定义在于ViewGroup中,默认返回值为false,表示不拦截TouchEvent()。onTouchEvent()方法定义在View中,当ViewGroup要调用onTouchEvent()时,调用super.onTouchEvent()方法。ViewGroup调用onTouchEvent()默认返回false,表示不消耗touch事件,View调用onTouchEvent()默认返回true,表示消耗了touch事件。

到这里我们把自定义视图会常遇到的方法都大致总结了一下。接下来再进行分析,因为有了这些方法,但我们还不知道它们调用的顺序,只有清楚了这些后才能做出更好的自定义视图。

首先建立自定义视图如下,继承View父类,然后打印出各种方法。

public class CustomView extends View {    public CustomView(Context context) {        super(context);        Log.i("anumbrella","constructor1");    }    public CustomView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        Log.i("anumbrella","constructor2");    }    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        Log.i("anumbrella","constructor3");    }    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        Log.i("anumbrella","constructor3");    }    @Override    protected void onAttachedToWindow() {        super.onAttachedToWindow();        Log.i("anumbrella","onAttachedToWindow()");    }    @Override    protected void onConfigurationChanged(Configuration newConfig) {        super.onConfigurationChanged(newConfig);        Log.i("anumbrella","onConfigurationChanged()");    }    @Override    protected void dispatchDraw(Canvas canvas) {        super.dispatchDraw(canvas);        Log.i("anumbrella","dispatchDraw()");    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        Log.i("anumbrella","onDraw()");    }    @Override    public void draw(Canvas canvas) {        super.draw(canvas);        Log.i("anumbrella","draw()");    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        Log.i("anumbrella","onLayout()");    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        Log.i("anumbrella","onMeasure()");    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        Log.i("anumbrella", "onSizeChanged() " + " w = " + w + "  h = " + h + "  oldW = " + oldw + "  oldH = " + oldw);    }}

结果如下图:

我们可以看到自定义view执行的方法顺序为:constructor2->onAttachedToWindow()->onMeasure()->onSizeChanged()->onLayout()->onMeasure()->onLayout()->onDraw()->dispatchDraw()->draw()。

现在我们将上面的代码改为继承VIewGroup(如:RelativeLayout)的视图控件,再添加onTouchEvent()和onInterceptTouchEvent()方法,再运行。
如下:

为啥没有调用draw()方法?因为ViewGroup没有背景颜色,这就跟上面总结的一样的。我们加上背景颜色android:background=”@color/colorPrimary”。重新运行,结果可以看到调用了draw()方法:

然后我们在自定义的ViewGroup下面添加子视图,比如TextView重新运行,点击时就会调用onTouchEvent()方法。

这里只是对自定义视图会用到的方法进行了简单的介绍,更深入的了解在此不做过多介绍。

好了,接下来我们才要开始进入主题——自定义的水波纹实现效果。有了上面的知识,我相信对下面的代码理解就会容易多了。

先来看看效果:

具体的代码如下,我们接下来一步一步介绍:

public class RippleView extends RelativeLayout {    /**     * 水波纹的颜色     */    private int rippleColor;    /**     * 水波纹扩散类型     */    private Integer rippleType;    /**     * 放大持续时间     */    private int zoomDuration;    /**     * 放大比例     */    private float zoomScale;    /**     * 放大动画类     */    private ScaleAnimation scaleAnimation;    /**     * 视图是否放大     */    private Boolean hasToZoom;    /**     * 是否从视图中心开始动画     */    private Boolean isCentered;    /**     * 帧速率     */    private int frameRate = 10;    /**     * 水波纹持续时间     */    private int rippleDuration = 400;    /**     * 水波纹透明度     */    private int rippleAlpha = 90;    /**     * canvas画布执行Handler     */    private Handler canvasHandler;    /**     * 水波纹画笔     */    private Paint paint;    /**     * 水波纹扩散内边距     */    private int ripplePadding;    /**     * 手势监听类     */    private GestureDetector gestureDetector;    /**     * 水波纹动画是否开始     */    private boolean animationRunning = false;    /**     * 时间统计     */    private int timer = 0;    /**     * 时间间隔     */    private int timerEmpty = 0;    /**     * 水波纹持续时间间隔     */    private int durationEmpty = -1;    /**     * 最大圆半径     */    private float radiusMax = 0;    /**     * 水波纹圆的坐标点     */    private float x = -1;    private float y = -1;    private Bitmap originBitmap;    private OnRippleCompleteListener onCompletionListener;    /**     * 视图的宽和高     */    private int WIDTH;    private int HEIGHT;    /**     * 定义水波纹类型     */    public enum RippleType {        SIMPLE(0),        DOUBLE(1),        RECTANGLE(2);        int type;        RippleType(int type) {            this.type = type;        }    }    /**     * 水波纹更新波纹Runnable     */    private final Runnable runnable = new Runnable() {        @Override        public void run() {            invalidate();        }    };    /**     * 定义回调函数,当水波纹效果完成时调用     */    public interface OnRippleCompleteListener {        void onComplete(RippleView rippleView);    }    public RippleView(Context context) {        super(context);    }    public RippleView(Context context, AttributeSet attrs) {        super(context, attrs);        init(context, attrs);    }    public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs);    }    /**     * 初始化方法     *     * @param context     * @param attrs     */    private void init(Context context, AttributeSet attrs) {        if (isInEditMode()) {            return;        }        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleView);        rippleColor = typedArray.getColor(R.styleable.RippleView_rv_color, getResources().getColor(R.color.rippelColor));        rippleType = typedArray.getInt(R.styleable.RippleView_rv_type, 0);        hasToZoom = typedArray.getBoolean(R.styleable.RippleView_rv_zoom, false);        isCentered = typedArray.getBoolean(R.styleable.RippleView_rv_centered, false);        rippleDuration = typedArray.getInteger(R.styleable.RippleView_rv_rippleDuration, rippleDuration);        rippleAlpha = typedArray.getInteger(R.styleable.RippleView_rv_alpha, rippleAlpha);        ripplePadding = typedArray.getDimensionPixelSize(R.styleable.RippleView_rv_ripplePadding, 0);        canvasHandler = new Handler();        zoomScale = typedArray.getFloat(R.styleable.RippleView_rv_zoomScale, 1.03f);        zoomDuration = typedArray.getInt(R.styleable.RippleView_rv_zoomDuration, 200);        typedArray.recycle();        paint = new Paint();        paint.setAntiAlias(true);        paint.setStyle(Paint.Style.FILL);        paint.setColor(rippleColor);        paint.setAlpha(rippleAlpha);        //使onDraw方法可以调用,以便被我们重写        this.setWillNotDraw(false);        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {            @Override            public void onLongPress(MotionEvent event) {                super.onLongPress(event);            }            @Override            public boolean onSingleTapConfirmed(MotionEvent e) {                return true;            }            @Override            public boolean onSingleTapUp(MotionEvent e) {                return true;            }        });        //开启cache来绘制视图        this.setDrawingCacheEnabled(true);        this.setClickable(true);    }    /**     * 绘制水波纹     *     * @param canvas     */    @Override    public void draw(Canvas canvas) {        super.draw(canvas);        if (animationRunning) {            canvas.save();            if (rippleDuration <= timer * frameRate) {                animationRunning = false;                timer = 0;                durationEmpty = -1;                timerEmpty = 0;                //android 23 会自动调用canvas.restore();                if (Build.VERSION.SDK_INT != 23) {                    canvas.restore();                }                invalidate();                if (onCompletionListener != null) {                    onCompletionListener.onComplete(this);                }                return;            } else {                canvasHandler.postDelayed(runnable, frameRate);            }            if (timer == 0) {                canvas.save();            }            canvas.drawCircle(x, y, (radiusMax * (((float) timer * frameRate) / rippleDuration)), paint);            paint.setColor(Color.parseColor("#ffff4444"));            if (rippleType == 1 && originBitmap != null && (((float) timer * frameRate) / rippleDuration) > 0.4f) {                if (durationEmpty == -1) {                    durationEmpty = rippleDuration - timer * frameRate;                }                timerEmpty++;                final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * frameRate) / (durationEmpty))));                canvas.drawBitmap(tmpBitmap, 0, 0, paint);                tmpBitmap.recycle();            }            paint.setColor(rippleColor);            if (rippleType == 1) {                if ((((float) timer * frameRate) / rippleDuration) > 0.6f) {                    paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timerEmpty * frameRate) / (durationEmpty)))));                } else {                    paint.setAlpha(rippleAlpha);                }            } else {                paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timer * frameRate) / rippleDuration))));            }            timer++;        }    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        WIDTH = w;        HEIGHT = h;        scaleAnimation = new ScaleAnimation(1.0f, zoomScale, 1.0f, zoomScale, w / 2, h / 2);        scaleAnimation.setDuration(zoomDuration);        scaleAnimation.setRepeatMode(Animation.REVERSE);        scaleAnimation.setRepeatCount(1);    }    /**     * 启动水波纹动画,通过MotionEvent事件     *     * @param event     */    public void animateRipple(MotionEvent event) {        createAnimation(event.getX(), event.getY());    }    /**     * 启动水波纹动画,通过x,y坐标     *     * @param x     * @param y     */    public void animateRipple(final float x, final float y) {        createAnimation(x, y);    }    private void createAnimation(final float x, final float y) {        if (this.isEnabled() && !animationRunning) {            if (hasToZoom) {                this.startAnimation(scaleAnimation);            }            radiusMax = Math.max(WIDTH, HEIGHT);            if (rippleType != 2) {                radiusMax /= 2;            }            radiusMax -= ripplePadding;            if (isCentered || rippleType == 1) {                this.x = getMeasuredWidth() / 2;                this.y = getMeasuredHeight() / 2;            } else {                this.x = x;                this.y = y;            }            animationRunning = true;            if (rippleType == 1 && originBitmap == null) {                originBitmap = getDrawingCache(true);            }            invalidate();        }    }    @Override    public boolean onTouchEvent(MotionEvent event) {        if (gestureDetector.onTouchEvent(event)) {            animateRipple(event);            sendClickEvent(false);        }        return super.onTouchEvent(event);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent event) {        this.onTouchEvent(event);        return super.onInterceptTouchEvent(event);    }    /**     * 发送一个点击事件,如果父视图是ListView实例     *     * @param isLongClick     */    private void sendClickEvent(final Boolean isLongClick) {        if (getParent() instanceof AdapterView) {            final AdapterView adapterView = (AdapterView) getParent();            final int position = adapterView.getPositionForView(this);            final long id = adapterView.getItemIdAtPosition(position);            if (isLongClick) {                if (adapterView.getOnItemLongClickListener() != null) {                    adapterView.getOnItemLongClickListener().onItemLongClick(adapterView, this, position, id);                }            } else {                if (adapterView.getOnItemClickListener() != null) {                    adapterView.getOnItemClickListener().onItemClick(adapterView, this, position, id);                }            }        }    }    /**     * 设置水波纹的颜色     *     * @param rippleColor     */    public void setRippleColor(int rippleColor) {        this.rippleColor = getResources().getColor(rippleColor);    }    public int getRippleColor() {        return rippleColor;    }    public RippleType getRippleType() {        return RippleType.values()[rippleType];    }    /**     * 设置水波纹动画类型,默认为RippleType.SIMPLE     *     * @param rippleType     */    public void setRippleType(final RippleType rippleType) {        this.rippleType = rippleType.ordinal();    }    public Boolean isCentered() {        return isCentered;    }    /**     * 设置水波纹动画是否开始从父视图中心开始,默认为false     *     * @param isCentered     */    public void setCentered(final Boolean isCentered) {        this.isCentered = isCentered;    }    public int getRipplePadding() {        return ripplePadding;    }    /**     * 设置水波纹内边距,默认为0dip     *     * @param ripplePadding     */    public void setRipplePadding(int ripplePadding) {        this.ripplePadding = ripplePadding;    }    public Boolean isZooming() {        return hasToZoom;    }    /**     * 在水波纹结束后,是否有放大动画,默认为false     *     * @param hasToZoom     */    public void setZooming(Boolean hasToZoom) {        this.hasToZoom = hasToZoom;    }    public float getZoomScale() {        return zoomScale;    }    /**     * 设置放大动画比例     *     * @param zoomScale     */    public void setZoomScale(float zoomScale) {        this.zoomScale = zoomScale;    }    public int getZoomDuration() {        return zoomDuration;    }    /**     * 设置放大动画持续时间,默认为200ms     *     * @param zoomDuration     */    public void setZoomDuration(int zoomDuration) {        this.zoomDuration = zoomDuration;    }    public int getRippleDuration() {        return rippleDuration;    }    /**     * 设置水波纹动画持续时间,默认为400ms     *     * @param rippleDuration     */    public void setRippleDuration(int rippleDuration) {        this.rippleDuration = rippleDuration;    }    public int getFrameRate() {        return frameRate;    }    /**     * 设置水波纹动画的帧速率,默认为10     *     * @param frameRate     */    public void setFrameRate(int frameRate) {        this.frameRate = frameRate;    }    public int getRippleAlpha() {        return rippleAlpha;    }    /**     * 设置水波纹动画的透明度,默认为90,取值为0到255之间     *     * @param rippleAlpha     */    public void setRippleAlpha(int rippleAlpha) {        this.rippleAlpha = rippleAlpha;    }    public void setOnRippleCompleteListener(OnRippleCompleteListener listener) {        this.onCompletionListener = listener;    }    /**     * 绘制扩散背景范围视图bitmap     *     * @param radius     * @return     */    private Bitmap getCircleBitmap(final int radius) {        final Bitmap output = Bitmap.createBitmap(originBitmap.getWidth(), originBitmap.getHeight(), Bitmap.Config.ARGB_8888);        final Canvas canvas = new Canvas(output);        final Paint paint = new Paint();        final Rect rect = new Rect((int) (x - radius), (int) (y - radius), (int) (x + radius), (int) (y + radius));        paint.setAntiAlias(true);        canvas.drawARGB(0, 0, 0, 0);        canvas.drawCircle(x, y, radius, paint);        //出来两图交叉情况        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));        canvas.drawBitmap(originBitmap, rect, rect, paint);        return output;    }}

定义RippleView类继承RelativeLayout。为啥是RelativeLayout?觉得可能比LinearLayout耗性能,但是它的扩展性毕竟好很多。所以这点还是可以忽略的,其次通过构造函数,引入attr下的style属性。

attr下的属性为:

     <declare-styleable name="RippleView">                <attr name="rv_alpha" format="integer" />                <attr name="rv_framerate" format="integer" />                <attr name="rv_rippleDuration" format="integer" />                <attr name="rv_zoomDuration" format="integer" />                <attr name="rv_color" format="color" />                <attr name="rv_centered" format="boolean" />                <attr name="rv_type" format="enum">            <enum name="simpleRipple" value="0" />            <enum name="doubleRipple" value="1" />            <enum name="rectangle" value="2" />        attr>                <attr name="rv_ripplePadding" format="dimension" />                <attr name="rv_zoom" format="boolean" />                <attr name="rv_zoomScale" format="float" />    declare-styleable>

有了这些后,我们就在init()函数中获取定义的属性,如果没有获取到
XML中定义的属性就设置为默认的值。然后调用onMeasure()来获取背
景视图的大小,再调用draw()方法去绘制。

在draw()方法中,一开始animationRunning是false,所以不会执行任何操作。

当我们点击视图时,这个onTouchEvent()方法就会调用,获取具体的x,y坐标,然后对radiusMax、animationRunning变量进行设置。
这个时候animationRunning为ture。最后调用invalidate(),重新draw()开始绘制。

在draw()中判断时间是否结束了,没有结束就通过canvasHandler来不停更新视图,调用draw()重新绘制视图。同时每次timer都会进行增加1,然后我们就通过timer的改变来实现半径大小的变化。每次绘制圆就形成了水波纹。

当要实现不同效果的水波纹时,即rippleType的值不一样时。就可以通过绘制不同的背景效果,改变透明度来实现。

好了,结束了。这就是水波纹自定义视图的大致实现,水波纹演示代码。

更多相关文章

  1. Android(安卓)之 Window、WindowManager 与窗口管理
  2. 范例解析:学习Android的IPC主板模式
  3. Android(安卓)文件保存getFilesDir()丶getCacheDir()、getExtern
  4. 范例解析:学习Android的IPC主板模式
  5. Android应用程序的Life Cycle
  6. Android(安卓)onTouchEvent, onClick及onLongClick的调用机制
  7. 当我们按下电源键,Android(安卓)究竟做了些什么?
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. 我家云/粒子云Rockchip RK3328将Android
  2. Android(安卓)Intent传递对象的两种方法(
  3. Android之TextView实现文字过长时省略部
  4. android 读取文件相关
  5. android语言三
  6. Android.Could not find *.apk in androi
  7. 在eclipse里卸载已安装的插件[例如Androi
  8. GreenDao 3.3.0 增删改查的使用(三)
  9. Android(安卓)Room牛刀小试
  10. Android(安卓)流式布局