zoukankan      html  css  js  c++  java
  • Android MaterialDesign之水波点击效果的几种实现方法

    什么是水波点击的效果? 下面是几种不同的实现方法的效果图以及实现方法

     
    Video_2016-08-31_003846

    如何实现?

    方法一 使用官方提供的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类实现" />
      

      效果:

       
      Video_2016-08-30_231558

      很简单的录制了下gif ,质量不好。实际效果很好看。

      注意事项:如果你的api最低版本低于21,则ripple这里其实是有错误提醒的,低于这个版本会报错。

    方法二 使用代码实现

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

    缺点:效果不如官方的好

    步骤:

    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);
              mCircleScale = typedArray.getFloat(R.styleable.MaterialLayout_scale, DEFAULT_SCALE);
      
              typedArray.recycle();
      
          }
      
          private void initPaint() {
              mPaint.setAntiAlias(true);
              mPaint.setStyle(Paint.Style.FILL);
              mPaint.setColor(mCirclelColor);
              mPaint.setAlpha(mColorAlpha);
      
              // 备份alpha属性用于动画完成时重置
              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>
      

      效果:

       
      Video_2016-08-30_235950

      其实可以看到,只要在 MaterialLayout 布局中的控件都有这个效果,所以使用其他是很方便的。

      这部分代码见原作者博客 http://blog.csdn.net/bboyfeiyu/article/details/42587799

    方法三 第三方库实现

    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文件

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

      效果:

       
      Video_2016-08-31_003808

    最后附上整个示例工程代码:

    https://github.com/liangddyy/RippleDemo

    原文:http://539go.com/2016/08/31/Android-MaterialDesign%E4%B9%8B%E6%B0%B4%E6%B3%A2%E7%82%B9%E5%87%BB%E6%95%88%E6%9E%9C%E7%9A%84%E5%87%A0%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95/



     
  • 相关阅读:
    关闭各种浏览器自动更新的方法
    新中新question
    linux忘记root密码后的解决办法
    Linux下的命令
    windows未启用Administrator账户
    Jenkins安装以及邮件配置
    pl/sql不能复制粘贴
    python-装饰器&自动化框架搭建
    进程练习——生成指定规格的图片
    python-类的各种方法
  • 原文地址:https://www.cnblogs.com/yelanggu/p/9933134.html
Copyright © 2011-2022 走看看