2017年特意写第一篇博客开个头。之前一直不写博客的原因,一个是懒,另一个是因为觉得自己没那个能力,会误导别人。但是最近特别想听听别人的意见,所以就下定决心开始写博客。废话不说,赶快开波。
前言
用过Toolbar的都知道标题是在左边的,但通常UI都要求我们把标题居中。没用过的童鞋可以看看泡网的一篇文章android:ToolBar详解(手把手教程)。我并不想自己写一个布局来充当Toolbar,因为那样会增加不少工作量。但怎样才能把Toolbar的标题居中?
有问题先google。如果你还在百度的话,我也没什么办法了。谷歌后找到这样一种方法。在原来的Toolbar里面加上一个TextView,标题居中的关键是第12行,将layout_gravity设置成center。
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:id="@+id/toolbar_title"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</android.support.v7.widget.Toolbar>
接着再来个helper类。很简单的一个类,看看init方法就可以过了。
public class ToolbarHelper {
private TextView mTitleTextView;
private Toolbar mToolbar;
public void init(AppCompatActivity activity, Toolbar toolbar, TextView titleTextView) {
if (activity == null) {
return;
}
mTitleTextView = titleTextView;
mToolbar = toolbar;
activity.setSupportActionBar(toolbar);
// titleTextView不为null才设置actionbar不显示Title
if (titleTextView != null) {
ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null) {
// 不显示title和subTitle
actionBar.setDisplayShowTitleEnabled(false);
}
}
}
public void setTitle(@StringRes int resid) {
if (mTitleTextView != null) {
mTitleTextView.setText(resid);
}
}
public void setTitle(CharSequence title) {
if (mTitleTextView != null) {
mTitleTextView.setText(title);
}
}
public void setTitleTextColor(@ColorInt int color) {
if (mTitleTextView != null) {
mTitleTextView.setTextColor(color);
}
}
public Toolbar getToolbar() {
return mToolbar;
}
public TextView getTitleTextView() {
return mTitleTextView;
}
}
然后在Activity中,最好是BaseActivity,创建一个ToolbarHelper
对象。在初始化View的时候,调用toolbarHelper的init()方法。
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
TextView titleView = (TextView) findViewById(R.id.toolbar_title);
mToolbarHelper.init(this, toolbar, titleView);
接下来才是重点,重写onTitleChanged方法
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
if (mToolbarHelper != null) {
mToolbarHelper.setTitle(title);
}
}
这样标题居中的Toolbar就搞定了。但有一点值得注意的是,不要调用toolbar自身的setTitle()方法。否则的话Toolbar自身的标题也会显示。
重点来了,CenterTitleToolbar
这样就满足了吗?不,上面这种方法太麻烦了。又要在xml里面加东西,又要改activity。能不能只改动Toolbar里面的代码,不需要动Toolbar以外的任何代码就能实现标题居中?
基于这个思路,我们要创建一个Toolbar的子类。使Toolbar的title居中有两种做法。
- 通过反射获取其
mTitleTextView
属性,并修改其LayoutParams的gravity属性 - 重写toolbar的setTitle方法,自己维护一个titleTextView,并将其居中显示。
这里我选择第二种。因为第一种使用了反射,且mTitleTextView
是通过懒加载形式创建的,要考虑好反射获取的时机。OK,想好了就开工了。
先来看看Toolbar源码里的setTitle()方法
public void setTitle(CharSequence title) {
if (!TextUtils.isEmpty(title)) {
if (mTitleTextView == null) {
final Context context = getContext();
mTitleTextView = new TextView(context);
mTitleTextView.setSingleLine();
mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
if (mTitleTextAppearance != 0) {
mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
}
if (mTitleTextColor != 0) {
mTitleTextView.setTextColor(mTitleTextColor);
}
}
if (!isChildOrHidden(mTitleTextView)) {
addSystemView(mTitleTextView, true);
}
} else if (mTitleTextView != null && isChildOrHidden(mTitleTextView)) {
removeView(mTitleTextView);
mHiddenViews.remove(mTitleTextView);
}
if (mTitleTextView != null) {
mTitleTextView.setText(title);
}
mTitleText = title;
}
看到第3-14行就知道我刚刚为什么说mTitleTextView
是通过懒加载来创建了吧。其他都看懂了,isChildOrHidden,addSystemView这两个方法做了什么?继续看源码。
// 判断该view的父View是否为Toolbar
private boolean isChildOrHidden(View child) {
return child.getParent() == this || mHiddenViews.contains(child);
}
// 创建LayoutParams对象,并添加到Toolbar
private void addSystemView(View v, boolean allowHide) {
final ViewGroup.LayoutParams vlp = v.getLayoutParams();
final LayoutParams lp;
if (vlp == null) {
lp = generateDefaultLayoutParams();
} else if (!checkLayoutParams(vlp)) {
lp = generateLayoutParams(vlp);
} else {
lp = (LayoutParams) vlp;
}
lp.mViewType = LayoutParams.SYSTEM;
if (allowHide && mExpandedActionView != null) {
v.setLayoutParams(lp);
mHiddenViews.add(v);
} else {
addView(v, lp);
}
}
这两个方法中都有mHiddenViews
,这个对于我们的需求没什么用,忽略就好了。不必每句都看懂,我们知道它大概做了什么即可。
OK,开始写我们的CenterTitleToolbar。先继承Toolbar,声明和Title相关的属性
private TextView mTitleTextView;
private CharSequence mTitleText;
private int mTitleTextColor;
private int mTitleTextAppearance;
重写setTitle()方法。
@Override
public void setTitle(CharSequence title) {
if (!TextUtils.isEmpty(title)) {
if (mTitleTextView == null) { // 懒加载
final Context context = getContext();
mTitleTextView = new TextView(context);
mTitleTextView.setSingleLine();
mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
if (mTitleTextAppearance != 0) {
mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
}
if (mTitleTextColor != 0) {
mTitleTextView.setTextColor(mTitleTextColor);
}
}
if (mTitleTextView.getParent() != this) {
// 添加到Toolbar并居中显示
addCenterView(mTitleTextView);
}
} else if (mTitleTextView != null && mTitleTextView.getParent() == this) {
// 当title为空时,remove
removeView(mTitleTextView);
}
if (mTitleTextView != null) {
mTitleTextView.setText(title);
}
mTitleText = title;
}
依照原来的思路稍微的修改。代码和原来的雷同,改动的地方就只有16,18,20和22行。关键是addCenterView这个方法。标题居中就看这里了。
private void addCenterView(View v) {
final ViewGroup.LayoutParams vlp = v.getLayoutParams();
final LayoutParams lp;
if (vlp == null) {
lp = generateDefaultLayoutParams();
} else if (!checkLayoutParams(vlp)) {
lp = generateLayoutParams(vlp);
} else {
lp = (LayoutParams) vlp;
}
lp.gravity = Gravity.CENTER;
addView(v, lp);
}
该方法与Toolbar的addSystemView()方法雷同,我基本都是直接拷过来改要改的地方,去掉了一些无用的代码。看到第12行,没错,我们前面做了那么多,就为了将gravity设置为center。
细心的童鞋会发现,setTitle()方法里还有mTitleTextAppearance 和 mTitleTextColor
需要获取。没错,要想获取这两个值,我们需要重写setTitleTextAppearance()和setTitleTextColor()方法,只不过是将原来Toolbar的代码拷过来,这里就不说了。除此之外,还需要在构建方法中自己获取一次。
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.Toolbar, defStyleAttr, 0);
try {
final int titleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
if (titleTextAppearance != 0) {
setTitleTextAppearance(context, titleTextAppearance);
}
if (a.hasValue(R.styleable.Toolbar_titleTextColor)) {
setTitleTextColor(a.getColor(R.styleable.Toolbar_titleTextColor, 0xffffffff));
}
} finally {
a.recycle();
}
这里解释下为什么已经重写了setTitleTextAppearance()和setTitleTextColor()方法还需要在构造方法中获取这两个值。因为Toolbar的源码获取TextAppearance并没有调用setTitleTextAppearance()方法,导致我们自己的mTitleTextAppearance
和Toolbar的不一致。还要注意下调用那两个方法的顺序。
最后
有什么错漏的麻烦指出。感谢大家。