点击Button实现水波纹和点击

方法一 使用官方提供的RippleDrawable类

优点:使用方便,非常漂亮。

缺点:Android5.0以下版本无法使用

步骤:

  1. 添加一个普通的 ripple_bg_drawable.xml 背景文件

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <solid android:color="#8cc476" /><!--背景颜色--》
        <corners android:radius="0dp" />
    </shape>
  2. 添加带波纹效果的背景文件 ripple_bg.xml

    <?xml version="1.0" encoding="utf-8"?>
    <ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#FF21272B">
        <item android:drawable="@drawable/ripple_bg_drawable" />
    </ripple>

    这里使用了上面的xml文件作为背景,然后给组件设置背景时,选 ripple_bg.xml 就可以了。如

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@drawable/ripple_bg"
        android:padding="10dp"
        android:text="使用RippleDrawable类实现" />

    效果:

    方法二 使用代码实现

    优点:低版本兼容、使用简单

    缺点:效果不如官方的好

    步骤:

    1. 在values下添加 attrs.xml 文件

       

      <?xml version="1.0" encoding="utf-8"?>

      <resources>

      <declare-styleable name="MaterialLayout">

      <attr name="alpha" format="integer" />

      <attr name="alpha_step" format="integer" />

      <attr name="framerate" format="integer" />

      <attr name="duration" format="integer" />

      <attr name="mycolor" format="color" />

      <attr name="scale" format="float" />

      </declare-styleable>

      </resources>

    2. 添加一个自定义的布局类 MaterialLayout.class

       

      public class MaterialLayout extends RelativeLayout {

      private static final int DEFAULT_RADIUS = 10;

      private static final int DEFAULT_FRAME_RATE = 10;

      private static final int DEFAULT_DURATION = 200;

      private static final int DEFAULT_ALPHA = 255;

      private static final float DEFAULT_SCALE = 0.8f;

      private static final int DEFAULT_ALPHA_STEP = 5;

      /**

      * 动画帧率

      */

      private int mFrameRate = DEFAULT_FRAME_RATE;

      /**

      * 渐变动画持续时间

      */

      private int mDuration = DEFAULT_DURATION;

      /**

      *

      */

      private Paint mPaint = new Paint();

      /**

      * 被点击的视图的中心点

      */

      private Point mCenterPoint = null;

      /**

      * 视图的Rect

      */

      private RectF mTargetRectf;

      /**

      * 起始的圆形背景半径

      */

      private int mRadius = DEFAULT_RADIUS;

      /**

      * 最大的半径

      */

      private int mMaxRadius = DEFAULT_RADIUS;

      /**

      * 渐变的背景色

      */

      private int mCirclelColor = Color.LTGRAY;

      /**

      * 每次重绘时半径的增幅

      */

      private int mRadiusStep = 1;

      /**

      * 保存用户设置的alpha值

      */

      private int mBackupAlpha;

      /**

      * 圆形半径针对于被点击视图的缩放比例,默认为0.8

      */

      private float mCircleScale = DEFAULT_SCALE;

      /**

      * 颜色的alpha值, (0, 255)

      */

      private int mColorAlpha = DEFAULT_ALPHA;

      /**

      * 每次动画Alpha的渐变递减值

      */

      private int mAlphaStep = DEFAULT_ALPHA_STEP;

      private View mTargetView;

      /**

      * @param context

      */

      public MaterialLayout(Context context) {

      this(context, null);

      }

      public MaterialLayout(Context context, AttributeSet attrs) {

      super(context, attrs);

      init(context, attrs);

      }

      public MaterialLayout(Context context, AttributeSet attrs, int defStyle) {

      super(context, attrs, defStyle);

      init(context, attrs);

      }

      private void init(Context context, AttributeSet attrs) {

      if (isInEditMode()) {

      return;

      }

      if (attrs != null) {

      initTypedArray(context, attrs);

      } initPaint();

      this.setWillNotDraw(false);

      this.setDrawingCacheEnabled(true);

      }

      private void initTypedArray(Context context, AttributeSet attrs) {

      final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialLayout);

      mCirclelColor = typedArray.getColor(R.styleable.MaterialLayout_mycolor, Color.LTGRAY);

      mDuration = typedArray.getInteger(R.styleable.MaterialLayout_duration, DEFAULT_DURATION);

      mFrameRate = typedArray.getInteger(R.styleable.MaterialLayout_framerate, DEFAULT_FRAME_RATE);

      mColorAlpha = typedArray.getInteger(R.styleable.MaterialLayout_alpha, DEFAULT_ALPHA);

      typedArray.recycle();

      mCircleScale = typedArray.getFloat(R.styleable.MaterialLayout_scale, DEFAULT_SCALE); }

      private void initPaint() {

      mPaint.setAntiAlias(true);

      mPaint.setStyle(Paint.Style.FILL);

      mPaint.setColor(mCirclelColor);

      // 备份alpha属性用于动画完成时重置

      mPaint.setAlpha(mColorAlpha);

      mBackupAlpha = mColorAlpha;

      }

      /**

      * 点击的某个坐标点是否在View的内部

      *

      * @param touchView

      * @param x 被点击的x坐标

      * @param y 被点击的y坐标

      * @return 如果点击的坐标在该view内则返回true,否则返回false

      */

      private boolean isInFrame(View touchView, float x, float y) {

      initViewRect(touchView);

      return mTargetRectf.contains(x, y);

      }

      /**

      * 获取点中的区域,屏幕绝对坐标值,这个高度值也包含了状态栏和标题栏高度

      *

      * @param touchView

      */

      private void initViewRect(View touchView) {

      int[] location = new int[2];

      touchView.getLocationOnScreen(location);

      // 视图的区域

      mTargetRectf = new RectF(location[0], location[1], location[0] + touchView.getWidth(),

      location[1] + touchView.getHeight());

      }

      /**

      * 减去状态栏和标题栏的高度

      */

      private void removeExtraHeight() {

      int[] location = new int[2];

      this.getLocationOnScreen(location);

      // 减去两个该布局的top,这个top值就是状态栏的高度

      mTargetRectf.top -= location[1];

      mTargetRectf.bottom -= location[1];

      // 计算中心点坐标

      int centerHorizontal = (int) (mTargetRectf.left + mTargetRectf.right) / 2;

      int centerVertical = (int) ((mTargetRectf.top + mTargetRectf.bottom) / 2);

      // 获取中心点

      mCenterPoint = new Point(centerHorizontal, centerVertical);

      }

      private View findTargetView(ViewGroup viewGroup, float x, float y) {

      int childCount = viewGroup.getChildCount();

      // 迭代查找被点击的目标视图

      for (int i = 0; i < childCount; i++) {

      View childView = viewGroup.getChildAt(i);

      if (childView instanceof ViewGroup) {

      return findTargetView((ViewGroup) childView, x, y);

      } else if (isInFrame(childView, x, y)) { // 否则判断该点是否在该View的frame内

      return childView;

      }

      }

      return null;

      }

      private boolean isAnimEnd() {

      return mRadius >= mMaxRadius;

      }

      private void calculateMaxRadius(View view) {

      // 取视图的最长边

      int maxLength = Math.max(view.getWidth(), view.getHeight());

      // 计算Ripple圆形的半径

      mMaxRadius = (int) ((maxLength / 2) * mCircleScale);

      int redrawCount = mDuration / mFrameRate;

      // 计算每次动画半径的增值

      mRadiusStep = (mMaxRadius - DEFAULT_RADIUS) / redrawCount;

      // 计算每次alpha递减的值

      mAlphaStep = (mColorAlpha - 100) / redrawCount;

      }

      /**

      * 处理ACTION_DOWN触摸事件, 注意这里获取的是Raw x, y,

      * 即屏幕的绝对坐标,但是这个当屏幕中有状态栏和标题栏时就需要去掉这些高度,因此得到mTargetRectf后其高度需要减去该布局的top起点

      * ,也就是标题栏和状态栏的总高度. *

      * @param event

      */

      private void deliveryTouchDownEvent(MotionEvent event) {

      if (event.getAction() == MotionEvent.ACTION_DOWN) {

      mTargetView = findTargetView(this, event.getRawX(), event.getRawY());

      if (mTargetView != null) {

      removeExtraHeight();

      // 计算相关数据

      calculateMaxRadius(mTargetView);

      // 重绘视图

      invalidate();

      } } }

      @Override

      public boolean onInterceptTouchEvent(MotionEvent event) {

      deliveryTouchDownEvent(event);

      return super.onInterceptTouchEvent(event);

      }

      @Override

      protected void dispatchDraw(Canvas canvas) {

      super.dispatchDraw(canvas);

      // 绘制Circle

      drawRippleIfNecessary(canvas);

      }

      private void drawRippleIfNecessary(Canvas canvas) {

      if (isFoundTouchedSubView()) {

      // 计算新的半径和alpha值

      mRadius += mRadiusStep;

      mColorAlpha -= mAlphaStep;

      // 裁剪一块区域,这块区域就是被点击的View的区域.通过clipRect来获取这块区域,使得绘制操作只能在这个区域范围内的进行,

      // 即使绘制的内容大于这块区域,那么大于这块区域的绘制内容将不可见. 这样保证了背景层只能绘制在被点击的视图的区域

      canvas.clipRect(mTargetRectf);

      mPaint.setAlpha(mColorAlpha);

      // 绘制背景圆形,也就是

      canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mPaint);

      }

      if (isAnimEnd()) {

      reset();

      } else {

      invalidateDelayed();

      } }

      /**

      * 发送重绘消息

      */

      private void invalidateDelayed() {

      this.postDelayed(new Runnable() {

      @Override

      public void run() {

      invalidate();

      } }, mFrameRate); }

      /**

      * 判断是否找到被点击的子视图

      *

      * @return

      */

      private boolean isFoundTouchedSubView() {

      return mCenterPoint != null && mTargetView != null;

      }

      private void reset() {

      mCenterPoint = null;

      mTargetRectf = null;

      mRadius = DEFAULT_RADIUS;

      mColorAlpha = mBackupAlpha;

      mTargetView = null;

      invalidate();

      }

      }

    3. 在布局文件中引用

       

      <com.liangddyy.rippledemo.MaterialLayout

      android:layout_width="wrap_content"

      android:layout_height="wrap_content">

      <LinearLayout

      android:layout_width="match_parent"

      android:layout_height="wrap_content"

      android:orientation="vertical">

      <Button

      android:layout_marginTop="10dp"

      android:layout_width="match_parent"

      android:layout_height="100dp"

      android:padding="10dp"

      android:text="自定义布局实现1" />

      <Button

      android:layout_marginTop="10dp"

      android:layout_width="match_parent"

      android:layout_height="100dp"

      android:padding="10dp"

      android:text="自定义布局实现2" />

      </LinearLayout>

      </com.liangddyy.rippledemo.MaterialLayout>

      效果:

      方法三 第三方库实现

      github上的一个叫 RippleEffec 项目

      优点:美观、使用简单、最低兼容API 9

      缺点:不如官方的好看啦

      步骤:

      1. 导入库

        dependencies {
            compile 'com.github.traex.rippleeffect:library:1.3'
        }
      2. 使用类似于方法二,比较灵活和方便

        <com.andexert.library.RippleView
            android:layout_marginTop="10dp"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            rv_centered="true">
            <Button
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:background="#4ce65e"
                android:text="第三方库实现"/>
        </com.andexert.library.RippleView>

      如果直接导入使用报错,那可以复制如下源码到工程使用。

      1. 定义个颜色

        <color name="rippelColor">#FFFFFF</color>

      2. 添加attrs.xml

        <?xml version="1.0" encoding="utf-8"?>
        <resources>
            <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>
        </resources>
      3. 添加RippleView.java文件

        代码太多,不复制了。参见末尾处的项目源码吧。

        效果:

        方法四

        在drawable下新建selector.xml文件:

                  

        1. <?xml version="1.0" encoding="utf-8"?>  
        2. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
        3.   
        4.     <item android:drawable="@drawable/button_press" android:state_pressed="true"/>  
        5.     <item android:drawable="@drawable/button_nomal" android:state_focused="false" android:state_pressed="false"/>  
        6.     <item android:drawable="@drawable/button_focus"  android:state_focused="true"/>  
        7.     <item android:drawable="@drawable/button_nomal" android:state_focused="false"/>  
        8.   
        9. </selector>  

         

               

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值