Android图文居中显示控件使用方法详解

最近项目中用到了文字图标的按钮,需要居中显示,如果用TextView实现的方式,必须同时设置padding和drawablePadding。如下:

<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" />

这种方式需要自己做精确计算。比较麻烦。另外还有一种方式就是用线性布局包裹ImageView和TextView,但这样会增加布局层级。于是自己封装了一个控件DrawableCenterTextView。

attrs.xml文件中定义属性:

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

对应的Java代码如下:

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;

  //计算文本范围
  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());
    }

    //加上左右内边距及drawable宽度和drawable间距
    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());
    }

    //加上上下内边距及drawable高度和drawable间距
    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(); //剩余垂直可绘制文本空间大小
  int hspace = getRight() - getLeft() - getCompoundPaddingRight() - getCompoundPaddingLeft(); //剩余水平可绘制文本空间大小

  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();
   //因为drawText以baseline为基准,因此需要向下移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);
  }
 }
}

感谢大家的支持,如有错误请指正。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android EditText搜索框实现图标居中

    类似这样EditText 搜索框,hiht 提示有一个icon并且text内容. 重写EditText : package mobi.truekey.weapp2.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.dr

  • Android中搜索图标和文字居中的EditText实例

    效果图: 需要自定义view,具体实现如下: import android.widget.EditText; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.uti

  • Android自定义TextView实现文字图片居中显示的方法

    最近有个需求是这样的,人民币的符号"¥"因为安卓手机系统的不一致导致符号不是完全一样,所以用美工的给的图片代替,考虑到用的地方比较多,所以想着写一个继承于线性布局的组合控件,后来一想,安卓中不是有TextView吗,这个自带图片的控件,后来写了个demo,因为我是用的MatchParent,导致问题出现,人民币符号不是和文字一样的居中,因此才有了这篇博文,让我们来自定义TextView吧,这个场景用的比较多. 分析下TextView的源码 我们先来分析下TextView的源码,因为Te

  • Android编程实现图片的浏览、缩放、拖动和自动居中效果

    本文实例讲述了Android编程实现图片的浏览.缩放.拖动和自动居中效果的方法.分享给大家供大家参考,具体如下: Touch.java /** * 图片浏览.缩放.拖动.自动居中 */ public class Touch extends Activity implements OnTouchListener { Matrix matrix = new Matrix(); Matrix savedMatrix = new Matrix(); DisplayMetrics dm; ImageVie

  • android imageview图片居中技巧应用

    做UI布局,尤其是遇到比较复杂的多重LinearLayout嵌套,常常会被一些比较小的问题困扰上半天,比如今天在使用ImageView的时候,想让其居中显示,可是无论怎样设置layout_gravity属性,都无法达到效果,部分代码如下: [java] 复制代码 代码如下: <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:

  • Android DrawableTextView图片文字居中显示实例

    在我们开发中,TextView设置Android:drawableLeft一定使用的非常多,但Drawable和Text同时居中显示可能不好控制,有没有好的办法解决呢? 小编的方案是通过自定义TextView实现. 实现的效果图: 注:第一行为原生TextView添加android:drawableLeft 第二行为自定义TextView实现的效果. 实现思路: 继承TextView,覆盖onDraw(Canvas canvas),在onDraw中先将canvas进行translate平移,再调

  • Android Canvas drawText文字居中的一些事(图解)

    1.写在前面 在实现自定义控件的过程中,常常会有绘制居中文字的需求,于是在网上搜了一些相关的博客,总是看的一脸懵逼,就想着自己分析一下,在此记录下来,希望对大家能够有所帮助. 2.绘制一段文本 首先把坐标原点移动到控件中心(默认坐标原点在屏幕左上角),这样看起来比较直观一些,然后绘制x.y轴,此时原点向上y为负,向下y为正,向左x为负,向右x为正,以(0,0)坐标开始绘制一段文本: @Override public void draw(Canvas canvas) { super.draw(ca

  • Android图文居中显示控件使用方法详解

    最近项目中用到了文字图标的按钮,需要居中显示,如果用TextView实现的方式,必须同时设置padding和drawablePadding.如下: <androidx.appcompat.widget.AppCompatTextView android:layout_width="200dp" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_xxx&quo

  • Android PickerScrollView滑动选择控件使用方法详解

    本文实例为大家分享了Android PickerScrollView滑动选择控件的具体使用代码,供大家参考,具体内容如下 先看一下效果图 1.SelectBean模拟假数据 public class SelectBean {       /**      * ret : 0      * msg : succes      * datas : [{"ID":"0","categoryName":"本人","state

  • Android滑动拼图验证码控件使用方法详解

    简介: 很多软件为了安全防止恶意攻击,会在登录/注册时进行人机验证,常见的人机验证方式有:谷歌点击复选框进行验证,输入验证码验证,短信验证码,语音验证,文字按顺序选择在图片上点击,滑动拼图验证等. 效果图: 代码实现: 1.滑块视图类:SlideImageView.java.实现随机选取拼图位置,对拼图位置进行验证等功能. public class SlideImageView extends View { Bitmap bitmap; Bitmap drawBitmap; Bitmap ver

  • Android中CheckBox复选框控件使用方法详解

    CheckBox复选框控件使用方法,具体内容如下 一.简介 1. 2.类结构图 二.CheckBox复选框控件使用方法 这里是使用java代码在LinearLayout里面添加控件 1.新建LinearLayout布局 2.建立CheckBox的XML的Layout文件 3.通过View.inflate()方法创建CheckBox CheckBox checkBox=(CheckBox) View.inflate(this, R.layout.checkbox, null); 4.通过Linea

  • Android中ToggleButton开关状态按钮控件使用方法详解

    ToggleButton开关状态按钮控件使用方法,具体内容如下 一.简介 1. 2.ToggleButton类结构 父类是CompoundButton,引包的时候注意下 二.ToggleButton开关状态按钮控件使用方法 1.新建ToggleButton控件及对象 private ToggleButton toggleButton1; toggleButton1=(ToggleButton) findViewById(R.id.toggleButton1); 2.设置setOnCheckedC

  • Android中SeekBar拖动条控件使用方法详解

    SeekBar拖动条控件使用方法,具体内容如下 一.简介 1.  二.SeekBar拖动条控件使用方法 1.创建SeekBar控件 <SeekBar android:id="@+id/SeekBar1" android:layout_width="match_parent" android:layout_height="wrap_content" android:progress="30" /> 2.添加setOn

  • HorizontalScrollView水平滚动控件使用方法详解

    一.简介 用法ScrollView大致相同 二.方法 1)HorizontalScrollView水平滚动控件使用方法 1.在layout布局文件的最外层建立一个HorizontalScrollView控件 2.在HorizontalScrollView控件中加入一个LinearLayout控件,并且把它的orientation设置为horizontal 3.在LinearLayout控件中放入多个装有图片的ImageView控件 2)HorizontalScrollView和ScrollVie

  • Android开发之TimePicker控件用法实例详解

    本文实例分析了Android开发之TimePicker控件用法.分享给大家供大家参考,具体如下: 新建项目: New Android Project-> Project name:HelloSpinner Build Target:Android 2.2 Application name:HelloSpinner Package name:com.b510 Create Activity:MainActivity Min SDK Version:9 Finish 运行效果: 如果: return

  • layui-laydate时间日历控件使用方法详解

    本文实例为大家分享了laydate时间日历控件的使用方法,供大家参考,具体内容如下 layui下载地址:http://www.layui.com/ 此控件可使用layui或者独立版的layDate,两者初始化有些不同 在 layui 模块中使用layui.code <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>layDate快速使用</title&

  • OpenLayers3加载常用控件使用方法详解

    本文实例为大家分享了OpenLayers3加载常用控件使用的具体代码,供大家参考,具体内容如下 1. 前言 地图控件就是对地图的缩放.全屏.坐标显示控件等,方便我们对地图进行操作.OpenLayers 3 封装了很多常用的地图控件,例如地图导航.比例尺.鹰眼.测量工具等,这些控件都是基于ol.control.Control虚基类进行封装,ol.control.Control的子类为各类常用的地图控件,可以通过Map对象的Control参数进行设置或者通过addControl方法将控件添加到地图窗

随机推荐