Android RippleEffect波纹效果,重写ViewGroup

    Android RippleEffect波纹效果,重写ViewGroup

   一直觉得Material Design很美,一直琢磨着打算给公司项目也换成Material Design风格,这里先介绍一种重写RelativeLayout实现的RippleEffect的波纹效果。

       先来看看Demo效果(GIF做得不好,见谅):


 


       先来看实现的思路,可以重写Button,ImageView等View,也可以重写ViewGroup。不同的是,重写ViewGroup时,需要在dispatchTouchEvent拦截点击事件,确定到底是出发了哪一个子view,然后再在onTouchEvent中去触发,当然也可以忽略dispatchTouchEvent,那么整个ViewGroup都会有RippleEffect效果。

       实现RippleEffect波纹效果的方式就是drawCircle,点击控件时,记录下点击位置的x,y值,作为波纹触发的中心点(当然也可以直接设定波纹触发的中心点为当前被点击控件的中心),再根据当前被点击的View的长宽,初始化一些与波纹半径相关的一些参数,每隔一定的时间,我们就绘制一次,每次绘制时,绘制波纹半径会在上一次的基础上,有一定的增量,可以匀速增,加速增,减速增,如果当绘制波纹半径大于我们设定的范围时,就停止绘制,这样就实现了RippleEffect效果。当然,绘制时可以用invalidate()+Handler的方式或者直接使用postInvalidate()刷新界面 。

来看看一个GIT上的 RippleEffect代码:

 private void init(final Context context, final 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);
        frameRate = typedArray.getInteger(R.styleable.RippleView_rv_framerate, frameRate);
        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);
        this.setWillNotDraw(false);

        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public void onLongPress(MotionEvent event) {
                super.onLongPress(event);
                animateRipple(event);
                sendClickEvent(true);
            }

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                return true;
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }
        });

        this.setDrawingCacheEnabled(true);
        this.setClickable(true);
    }

       和其它自定义View或者ViewGroup一样,构造方法中调用此方法初始化自定义属性及一些Paint,canvas等,这里也初始化了gestureDetector,是为了在长按事件中也会触发RippleEffect波纹效果。

       看看attrs.xml中的自定义属性:

             
     <declare-styleable name="RippleView">rv_zoomDuration
        <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>  

  • rv_alpha即paint.setAlpha()中设定的值,就是透明度,其取值范围是0---255,数值越小,越透明,颜色上表现越淡。
  • rv_framerate是RippleEffect波纹每次半径增加相关的一个参数,值越大,每次的半径增量越大,效果看起来也就越粗糙。
  • rv_rippleDuration就是RippleEffect波纹的范围了,超过此范围,就不再绘制了。
  • rv_zoomDuration为RippleEffect波纹的持续时间。
  • rv_color为RippleEffect波纹的颜色。
  • rv_centered值为true,则波纹的触发点为View的中心,否则为触摸点的位置。
  • rv_type为波纹效果的类型,simpleRipple就是最常见的那种,只产生一圈涟漪效果,doubleRipple两圈涟漪,rectangle的效果代码中没具体实现。
  • rv_zoom值为true,会有一个缩放的效果,rv_ripplePadding为和RippleEffect波纹半径相关的一个参数,rv_zoomScale为缩放动画结束时Y坐标上的伸缩尺寸。

       最重要的draw方法:

  @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (animationRunning) {
            if (rippleDuration <= timer * frameRate) {
                animationRunning = false;
                timer = 0;
                durationEmpty = -1;
                timerEmpty = 0;
                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++;
        }
    }

       在初始化的时候, animationRunning值已被置为true,主要是为了保证一次完整地波纹效果不会被打断。 首先执行 canvasHandler.postDelayed(runnable, frameRate);每隔frameRate的时间,执行一次invalidate()。 canvas.save()用来保存Canvas的状态,注意在绘制结束的时候调用了canvas.restore(),成对使用。然后就开始绘制波纹了,每次的半径增量为(radiusMax * (((float) 1 * frameRate) / rippleDuration),每隔rameRate绘制一次。rippleType == 1的时候,保证在绘制到一定的时候,setAlpha的值加深,形成两次涟漪,否则setAlpha值就一直递减,波纹越大效果越弱。最后当rippleDuration <= timer * frameRate的时候,就return了,RippleEffect波纹效果结束。

       最后在XML中直接拿来用就可以了:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ripple="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="vertical" >

            <!-- 1 rv_centered="true" rv_type="simpleRipple" -->

            <com.example.RippleEffect.RippleView
                android:id="@+id/more"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_centered="true" >

                <ImageView
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:src="@drawable/ic_launcher" />
            </com.example.RippleEffect.RippleView>

            <!-- 2 rv_centered="false" rv_type="simpleRipple" -->

            <com.example.RippleEffect.RippleView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_centered="false"
                ripple:rv_type="simpleRipple" >

                <ImageView
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:src="@drawable/ic_launcher" />
            </com.example.RippleEffect.RippleView>

            <!-- 3 rv_type="doubleRipple" -->

            <com.example.RippleEffect.RippleView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_type="doubleRipple" >

                <ImageView
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:src="@drawable/ic_launcher" />
            </com.example.RippleEffect.RippleView>

            <!-- 4 rv_type="rectangle" -->

            <com.example.RippleEffect.RippleView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_type="doubleRipple" >

                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:src="@drawable/ic_launcher" />
            </com.example.RippleEffect.RippleView>

            <!-- 5  rv_zoom ="true" rv_ripplePadding ="20dp"   ripple:rv_zoomScale="1.25" -->

            <com.example.RippleEffect.RippleView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_centered="false"
                ripple:rv_color="#D91615"
                ripple:rv_rippleDuration="2000"
                ripple:rv_ripplePadding="20dp"
                ripple:rv_zoom="true"
                ripple:rv_zoomDuration="200"
                ripple:rv_zoomScale="1.25" >

                <ImageView
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:src="@drawable/ic_launcher" />
            </com.example.RippleEffect.RippleView>

            <!-- 6 rv_type="simpleRipple" rv_alpha="10" rv_framerate="100" -->

            <com.example.RippleEffect.RippleView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_alpha="200"
                ripple:rv_framerate="100"
                ripple:rv_type="simpleRipple" >

                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:src="@drawable/ic_launcher" />
            </com.example.RippleEffect.RippleView>

            <!-- 7 rv_type="simpleRipple" rv_alpha="10" rv_framerate="2" -->

            <com.example.RippleEffect.RippleView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_alpha="200"
                ripple:rv_framerate="2"
                ripple:rv_type="simpleRipple" >

                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:src="@drawable/ic_launcher" />
            </com.example.RippleEffect.RippleView>

            <!-- 8 rv_type="simpleRipple" rv_alpha="10" rv_framerate="2" -->

            <com.example.RippleEffect.RippleView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                ripple:rv_alpha="200"
                ripple:rv_framerate="2"
                ripple:rv_type="simpleRipple" >

                <Button
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:background="#BAC9FF"
                    android:padding="15dp"
                    android:text="Button" />
            </com.example.RippleEffect.RippleView>
        </LinearLayout>
    </ScrollView>

</LinearLayout>

DEMO 下载地址 http://download.csdn.net/detail/yalinfendou/8803431








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值