Android Graphic and Text Center Display Control Usage Detailed Explanation

  • 2021-12-09 09:50:08
  • OfStack

Recently, the text icon button is used in the project, which needs to be displayed in the center. If TextView is used, padding and drawablePadding must be set at the same time. As follows:


<androidx.appcompat.widget.AppCompatTextView
  android:layout_width="200dp"
  android:layout_height="wrap_content"
  android:drawableLeft="@drawable/ic_xxx"
  android:drawablePadding="-60dp"
  android:minHeight="48dp"
  android:gravity="center"
  android:padding="80dp" />

This method requires accurate calculation by yourself. It's more troublesome. Another way is to wrap ImageView and TextView in a linear layout, but this will increase the layout level. So I encapsulated a control DrawableCenterTextView myself.

Attributes defined in the attrs. xml file:


<declare-styleable name="DrawableCenterTextView">
  <attr name="android:text" />
  <attr name="android:textColor" />
  <attr name="android:textSize" />
  <attr name="android:textStyle" />
  <attr name="android:drawablePadding" />
  <attr name="android:drawableLeft" />
  <attr name="android:drawableTop" />
  <attr name="android:drawableRight" />
  <attr name="android:drawableBottom" />
</declare-styleable>

The corresponding Java code is as follows:


public class DrawableCenterTextView extends View {

 static final int LEFT = 0;
 static final int TOP = 1;
 static final int RIGHT = 2;
 static final int BOTTOM = 3;

 private CharSequence mText;
 private ColorStateList mTextColor;
 private float mTextSize;
 private int mTextStyle;
 private int mDrawablePadding;
 private Drawable[] mCompoundDrawables;

 private Rect mTextBounds;
 private Rect mDrawableLeftBounds;
 private Rect mDrawableTopBounds;
 private Rect mDrawableRightBounds;
 private Rect mDrawableBottomBounds;

 private TextPaint mTextPaint;

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

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

 public DrawableCenterTextView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);

  Drawable drawableLeft = null, drawableTop = null, drawableRight = null, drawableBottom = null;
  TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DrawableCenterTextView, defStyleAttr, 0);
  mText = ta.getText(R.styleable.DrawableCenterTextView_android_text);
  mTextColor = ta.getColorStateList(R.styleable.DrawableCenterTextView_android_textColor);
  mTextSize = ta.getDimensionPixelSize(R.styleable.DrawableCenterTextView_android_textSize, 15);
  mTextStyle = ta.getInt(R.styleable.DrawableCenterTextView_android_textStyle, 0);

  drawableLeft = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableLeft);
  drawableTop = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableTop);
  drawableRight = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableRight);
  drawableBottom = ta.getDrawable(R.styleable.DrawableCenterTextView_android_drawableBottom);

  mDrawablePadding = ta.getDimensionPixelSize(R.styleable.DrawableCenterTextView_android_drawablePadding, 0);
  ta.recycle();

  if (mTextColor == null) {
   mTextColor = ColorStateList.valueOf(0xFF000000);
  }

  mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
  mTextPaint.density = getResources().getDisplayMetrics().density;
  mTextPaint.setTextSize(mTextSize);
  setTypeface(Typeface.create(Typeface.DEFAULT, mTextStyle));

  setCompoundDrawablesWithIntrinsicBounds(drawableLeft, drawableTop, drawableRight, drawableBottom);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  int heightSize = MeasureSpec.getSize(heightMeasureSpec);

  int width;
  int height;

  // Calculate text range 
  calcTextBounds();

  if (widthMode == MeasureSpec.EXACTLY) {
   width = widthSize;
  } else {
   width = mTextBounds.width();

   if (mCompoundDrawables != null) {
    if (mCompoundDrawables[TOP] != null) {
     width = Math.max(width, mDrawableTopBounds.width());
    }

    if (mCompoundDrawables[BOTTOM] != null) {
     width = Math.max(width, mDrawableBottomBounds.width());
    }

    // Add left and right inner margins and drawable Width and drawable Spacing 
    width += getCompoundPaddingLeft() + getCompoundPaddingRight();

    width = Math.max(width, getSuggestedMinimumWidth());

    if (widthMode == MeasureSpec.AT_MOST) {
     width = Math.min(widthSize, width);
    }
   }
  }

  if (heightMode == MeasureSpec.EXACTLY) {
   height = heightSize;
  } else {
   height = mTextBounds.height();

   if (mCompoundDrawables != null) {
    if (mCompoundDrawables[LEFT] != null) {
     height = Math.max(height, mDrawableLeftBounds.height());
    }

    if (mCompoundDrawables[RIGHT] != null) {
     height = Math.max(height, mDrawableRightBounds.height());
    }

    // Add upper and lower inner margins and drawable Height and drawable Spacing 
    height += getCompoundPaddingTop() + getCompoundPaddingBottom();

    height = Math.max(height, getSuggestedMinimumHeight());

    if (heightMode == MeasureSpec.AT_MOST) {
     height = Math.min(heightSize, height);
    }
   }
  }

  setMeasuredDimension(width, height);
 }

 public int getCompoundPaddingTop() {
  if (mCompoundDrawables == null || mCompoundDrawables[TOP] == null) {
   return getPaddingTop();
  } else {
   Rect rect = new Rect();
   mCompoundDrawables[TOP].copyBounds(rect);
   return getPaddingTop() + mDrawablePadding + rect.height();
  }
 }

 public int getCompoundPaddingBottom() {
  if (mCompoundDrawables == null || mCompoundDrawables[BOTTOM] == null) {
   return getPaddingBottom();
  } else {
   Rect rect = new Rect();
   mCompoundDrawables[BOTTOM].copyBounds(rect);
   return getPaddingBottom() + mDrawablePadding + rect.height();
  }
 }

 public int getCompoundPaddingLeft() {
  if (mCompoundDrawables == null || mCompoundDrawables[LEFT] == null) {
   return getPaddingLeft();
  } else {
   Rect rect = new Rect();
   mCompoundDrawables[LEFT].copyBounds(rect);
   return getPaddingLeft() + mDrawablePadding + rect.width();
  }
 }

 public int getCompoundPaddingRight() {
  if (mCompoundDrawables == null || mCompoundDrawables[RIGHT] == null) {
   return getPaddingRight();
  } else {
   Rect rect = new Rect();
   mCompoundDrawables[RIGHT].copyBounds(rect);
   return getPaddingRight() + mDrawablePadding + rect.width();
  }
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  int vspace = getBottom() - getTop() - getCompoundPaddingBottom() - getCompoundPaddingTop(); // Size of remaining vertical renderable text space 
  int hspace = getRight() - getLeft() - getCompoundPaddingRight() - getCompoundPaddingLeft(); // Size of remaining horizontal renderable text space 

  if (mCompoundDrawables != null) {
   if (mCompoundDrawables[LEFT] != null) {
    canvas.save();
    canvas.translate((hspace - mTextBounds.width()) / 2.0f + getPaddingLeft(),
      getCompoundPaddingTop() + (vspace - mDrawableLeftBounds.height()) / 2.0f);
    mCompoundDrawables[LEFT].draw(canvas);
    canvas.restore();
   }

   if (mCompoundDrawables[RIGHT] != null) {
    canvas.save();
    canvas.translate(getRight() - getLeft() - getPaddingRight() - (hspace - mTextBounds.width()) / 2.0f - mDrawableRightBounds.width(),
      getCompoundPaddingTop() + (vspace - mDrawableRightBounds.height()) / 2.0f);
    mCompoundDrawables[RIGHT].draw(canvas);
    canvas.restore();
   }

   if (mCompoundDrawables[TOP] != null) {
    canvas.save();
    canvas.translate(getCompoundPaddingLeft()
      + (hspace - mDrawableTopBounds.width()) / 2.0f, (vspace - mTextBounds.height()) / 2.0f + getPaddingTop());
    mCompoundDrawables[TOP].draw(canvas);
    canvas.restore();
   }

   if (mCompoundDrawables[BOTTOM] != null) {
    canvas.save();
    canvas.translate(getCompoundPaddingLeft()
        + (hspace - mDrawableBottomBounds.width()) / 2.0f,
      getBottom() - getTop() - getPaddingBottom() - (vspace - mTextBounds.height()) / 2.0f - mDrawableBottomBounds.height());
    mCompoundDrawables[BOTTOM].draw(canvas);
    canvas.restore();
   }
  }

  if (!TextUtils.isEmpty(mText)) {
   float startX = (hspace - mTextBounds.width()) / 2.0f + getCompoundPaddingLeft();
   // Because drawText With baseline Is the baseline, so it needs to be moved down ascent
   float startY = (vspace - mTextBounds.height()) / 2.0f + getCompoundPaddingTop() - mTextPaint.getFontMetrics().ascent;
   mTextPaint.setColor(mTextColor.getColorForState(getDrawableState(), 0));
   canvas.drawText(mText, 0, mText.length(), startX, startY, mTextPaint);
  }
 }

 @Override
 protected void drawableStateChanged() {
  super.drawableStateChanged();

  if (mTextColor != null && mTextColor.isStateful()) {
   mTextPaint.setColor(mTextColor.getColorForState(getDrawableState(), 0));
  }

  if (mCompoundDrawables != null) {
   final int[] state = getDrawableState();
   for (Drawable dr : mCompoundDrawables) {
    if (dr != null && dr.isStateful() && dr.setState(state)) {
     invalidateDrawable(dr);
    }
   }
  }
 }

 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
              @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
  if (left != null) {
   left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
  }
  if (right != null) {
   right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
  }
  if (top != null) {
   top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
  }
  if (bottom != null) {
   bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
  }
  setCompoundDrawables(left, top, right, bottom);
 }

 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
          @Nullable Drawable right, @Nullable Drawable bottom) {

  if (mCompoundDrawables == null) {
   mCompoundDrawables = new Drawable[4];
  } else {
   if (mCompoundDrawables[LEFT] != null && mCompoundDrawables[LEFT] != left) {
    mCompoundDrawables[LEFT].setCallback(null);
   }

   if (mCompoundDrawables[TOP] != null && mCompoundDrawables[TOP] != top) {
    mCompoundDrawables[TOP].setCallback(null);
   }

   if (mCompoundDrawables[RIGHT] != null && mCompoundDrawables[RIGHT] != right) {
    mCompoundDrawables[RIGHT].setCallback(null);
   }

   if (mCompoundDrawables[BOTTOM] != null && mCompoundDrawables[BOTTOM] != bottom) {
    mCompoundDrawables[BOTTOM].setCallback(null);
   }
  }

  if (left != null) {
   mDrawableLeftBounds = new Rect();
   left.copyBounds(mDrawableLeftBounds);
   left.setCallback(this);
   mCompoundDrawables[LEFT] = left;
  } else {
   mCompoundDrawables[LEFT] = null;
  }

  if (top != null) {
   mDrawableTopBounds = new Rect();
   top.copyBounds(mDrawableTopBounds);
   top.setCallback(this);
   mCompoundDrawables[TOP] = top;
  } else {
   mCompoundDrawables[TOP] = null;
  }

  if (right != null) {
   mDrawableRightBounds = new Rect();
   right.copyBounds(mDrawableRightBounds);
   right.setCallback(this);
   mCompoundDrawables[RIGHT] = right;
  } else {
   mCompoundDrawables[RIGHT] = null;
  }

  if (bottom != null) {
   mDrawableBottomBounds = new Rect();
   bottom.copyBounds(mDrawableBottomBounds);
   bottom.setCallback(this);
   mCompoundDrawables[BOTTOM] = bottom;
  } else {
   mCompoundDrawables[BOTTOM] = null;
  }

  invalidate();
  requestLayout();
 }

 public void setText(CharSequence text) {
  this.mText = text;

  invalidate();
  requestLayout();
 }

 public void setTextSize(float textSize) {
  this.mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, getResources().getDisplayMetrics());

  invalidate();
  requestLayout();
 }

 public void setTextColor(@ColorInt int textColor) {
  this.mTextColor = ColorStateList.valueOf(textColor);

  invalidate();
 }

 public void setTypeface(@Nullable Typeface tf) {
  if (mTextPaint.getTypeface() != tf) {
   mTextPaint.setTypeface(tf);

   requestLayout();
   invalidate();
  }
 }

 private void calcTextBounds() {
  mTextBounds = new Rect();

  if (TextUtils.isEmpty(mText)) return;

  if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
   mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
  } else {
   int width = (int) Math.ceil(mTextPaint.measureText(mText.toString()));
   Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
   int height = (int) Math.ceil(fontMetrics.descent - fontMetrics.ascent);
   mTextBounds.set(0, 0, width, height);
  }
 }
}

Thank you for your support. Please correct any mistakes.


Related articles: