【源码解析】开源项目-FlycoRoundView

422 阅读2分钟

概述

  • 该库通过扩展原生控件实现圆角矩形背景,扩展的控件包括:

    • FrameLayout
    • LinearLayout
    • RelativeLayout
    • TextView
  • 实现效果如下:

    Image

  • 使用方式

    参考官方地址

源码解析

RoundTextView(其它实现类似)

public class RoundTextView extends TextView {
    private RoundViewDelegate delegate;

    public RoundTextView(Context context) {
        this(context, null);
    }

    public RoundTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RoundTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        delegate = new RoundViewDelegate(this, context, attrs);
    }

    /** use delegate to set attr */
    public RoundViewDelegate getDelegate() {
        return delegate;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (delegate.isWidthHeightEqual() && getWidth() > 0 && getHeight() > 0) {
            int max = Math.max(getWidth(), getHeight());
            int measureSpec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY);
            super.onMeasure(measureSpec, measureSpec);
            return;
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (delegate.isRadiusHalfHeight()) {
            delegate.setCornerRadius(getHeight() / 2);
        } else {
            delegate.setBgSelector();
        }
    }
}

可以看到具体是通过RoundViewDelegate实现。

在onMeasure中,当配置了属性isWidthHeightEqual时,会指定测量规格,以宽高大的一边测量为正文形;

在onLayout中,当配置了属性isRadiusHalfHeight时,圆角即为高度的一半;

RoundViewDelegate中的setter方法,都会调用setBgSelector来进行真正的背景绘制;

RoundViewDelegate

  • RoundViewDelegate中,有两个成员变量表示默认背景及按下背景;
private GradientDrawable gd_background = new GradientDrawable();
private GradientDrawable gd_background_press = new GradientDrawable();

注意:在xml使用shape时,编译对应的就是GradientDrawable,而不是ShapeDrawable

  • 接下来我们看一下setBgSelector方法

    1. 首先构建一个StateListDrawable用于表示背景Drawable

      StateListDrawable bg = new StateListDrawable();
      
    2. 判断api是否大于5.0,并且是否使用水波纹

       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isRippleEnable) {
                  setDrawable(gd_background, backgroundColor, strokeColor);
                  RippleDrawable rippleDrawable = new RippleDrawable(
                          getPressedColorSelector(backgroundColor, backgroundPressColor), gd_background, null);
                  view.setBackground(rippleDrawable);
              }
      

      进入该条件,首先会把成员变量gd_background传入setDrawable方法对其进行赋值

          private void setDrawable(GradientDrawable gd, int color, int strokeColor) {
              gd.setColor(color);
      
              if (cornerRadius_TL > 0 || cornerRadius_TR > 0 || cornerRadius_BR > 0 || cornerRadius_BL > 0) {
                  /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/
                  radiusArr[0] = cornerRadius_TL;
                  radiusArr[1] = cornerRadius_TL;
                  radiusArr[2] = cornerRadius_TR;
                  radiusArr[3] = cornerRadius_TR;
                  radiusArr[4] = cornerRadius_BR;
                  radiusArr[5] = cornerRadius_BR;
                  radiusArr[6] = cornerRadius_BL;
                  radiusArr[7] = cornerRadius_BL;
                  gd.setCornerRadii(radiusArr);
              } else {
                  gd.setCornerRadius(cornerRadius);
              }
      
              gd.setStroke(strokeWidth, strokeColor);
          }
      

      主要设置它的背景色、圆角以及边框;

      之后会生成一个带水波纹的Drawable(RippleDrawable),会调用getPressedColorSelector生成颜色状态;

          @TargetApi(Build.VERSION_CODES.HONEYCOMB)
          private ColorStateList getPressedColorSelector(int normalColor, int pressedColor) {
              return new ColorStateList(
                      new int[][]{
                              new int[]{android.R.attr.state_pressed},
                              new int[]{android.R.attr.state_focused},
                              new int[]{android.R.attr.state_activated},
                              new int[]{}
                      },
                      new int[]{
                              pressedColor,
                              pressedColor,
                              pressedColor,
                              normalColor
                      }
              );
          }
      }
      

      最终把RippleDrawable的实例设置为View的background。

    3. 如果不使用水波纹

      setDrawable(gd_background, backgroundColor, strokeColor);
      bg.addState(new int[]{-android.R.attr.state_pressed}, gd_background);
      if (backgroundPressColor != Integer.MAX_VALUE || strokePressColor != Integer.MAX_VALUE) {
                      setDrawable(gd_background_press, backgroundPressColor == Integer.MAX_VALUE ? backgroundColor : backgroundPressColor,
                              strokePressColor == Integer.MAX_VALUE ? strokeColor : strokePressColor);
                      bg.addState(new int[]{android.R.attr.state_pressed}, gd_background_press);
                  }
      
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {//16
          view.setBackground(bg);
      } else {
      		view.setBackgroundDrawable(bg);
         }
      }
      

      通过StateListDrawable分别设置不同状态的Drawable,再设置给background;

      bg.addState(new int[]{-android.R.attr.state_pressed}, gd_background);这行代码中出现负号**-**,表示state_pressed为false的情况。

    4. 最后TextView作特殊处理

              if (view instanceof TextView) {
                  if (textPressColor != Integer.MAX_VALUE) {
                      ColorStateList textColors = ((TextView) view).getTextColors();
                      ColorStateList colorStateList = new ColorStateList(
                              new int[][]{new int[]{-android.R.attr.state_pressed}, new int[]{android.R.attr.state_pressed}},
                              new int[]{textColors.getDefaultColor(), textPressColor});
                      ((TextView) view).setTextColor(colorStateList);
                  }
              }
      

      通过ColorStateList设置文字颜色。

      注意GradientDrawableStateListDrawableColorStateList的使用场景。