前言
因为这两个组件都是继承的BaselineGridTextView而且都比较短,就放在一起了。
AuthorTextView
废话不多说,先上源码:
/**
* An extension to TextView which supports a custom state of {@link #STATE_ORIGINAL_POSTER} for
* denoting that a comment author was the original poster.
*/
public class AuthorTextView extends BaselineGridTextView {
private static final int[] STATE_ORIGINAL_POSTER = { R.attr.state_original_poster };
private boolean isOP = false;
public AuthorTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isOP) {
mergeDrawableStates(drawableState, STATE_ORIGINAL_POSTER);
}
return drawableState;
}
public boolean isOriginalPoster() {
return isOP;
}
public void setOriginalPoster(boolean isOP) {
if (this.isOP != isOP) {
this.isOP = isOP;
refreshDrawableState();
}
}
}
需要一个attrs_author_text_view.xml来定义一个额外的属性:
<resources>
<declare-styleable name="AuthorTextView">
<attr name="state_original_poster" format="boolean|reference"/>
</declare-styleable>
</resources>
这个控件的目的很简单,就是多加一个状态isOP来表示某个评论是不是作者发的。当然在xml里面是state_original_poster这个属性。
实际上,Plaid里面并没怎么使用这个属性,至少就我看到的而言,虽然有设置isOP,但没有设置对应的Selector Drawable,因此也是白搭。
不过至少展示了如何自定义加一个状态。
网上查找了一些相关资料,都是比较古老的博客了,大多是2012年的。像这一篇是注释比较详细的:
public class PrivateModeButton extends Button {
// (Combination of) States are usually specified as an array.
// Our custom attribute will be generated as R.attr.state_private_mode.
// Note: This is in our app's scope.
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private_mode };
// The view needs a way to know if it's in private mode or not.
private boolean mIsPrivate = false;
public PrivateModeButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Android calls this method to know the current drawable state of the view.
// It starts with an "extraSpace" of 0 in View.java, and each inherited view adds its new state.
// We add just one more state, hence, we create a new array of size "extraSpace + 1".
@Override
public int[] onCreateDrawableState(int extraSpace) {
// Ask the parent to add its default states.
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
// If we are private, add the state to array of states.
// If not added, the value will be treated as false.
// mergeDrawableStates() takes care of resolving the duplicates.
if (mIsPrivate)
mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
// Return the new drawable state.
return drawableState;
}
// We need a way for the Activity (or some other part of the code)
// to enable private mode for the view.
public void setPrivateMode(boolean isPrivate) {
// If we flip the current state of private mode, record the value
// and inform Android to refresh the drawable state.
// This will in turn invalidate() the view.
if (mIsPrivate != isPrivate) {
mIsPrivate = isPrivate;
refreshDrawableState();
}
}
}
本来想展示一下效果的,结果经过一下午的尝试,最后还是没能成功达成想要的效果。
一切看上去很清晰,使用这么一个selector来当背景:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item custom:state_original_post="true" android:drawable="@android:color/holo_blue_bright"/>
<item android:drawable="@android:color/transparent"/>
</selector>
结果编译过不去,报No resource identifier found for attribute state_original_post...
然后我查资料,有的人说把第二个xmlns改为xmlns:custom="http://schemas.android.com/apk/lib/packageName"
这样确实能够编译,然后不管我怎么设置isOP,背景都是蓝色的。
我不知道Plaid这个app没有设置这个背景,是不是也是因为有bug呢?毕竟专门写了一个控件,都到了这个份上了,只要再定义一个背景就行了,却止步。
到这个时候,只能先跳过了,等以后再说。
DynamicTypeTextView
上源码:
/**
* An extension to {@link android.widget.TextView} which sizes text to grow up to a specified
* maximum size, per the material spec:
* https://www.google.com/design/spec/style/typography.html#typography-other-typographic-guidelines
*/
public class DynamicTypeTextView extends BaselineGridTextView {
// configurable attributes
private final float minTextSize;
private final float maxTextSize;
public DynamicTypeTextView(Context context) {
this(context, null);
}
public DynamicTypeTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public DynamicTypeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DynamicTypeTextView(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
/* re-use CollapsingTitleLayout attribs */
final TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.CollapsingTitleLayout);
if (a.hasValue(R.styleable.CollapsingTitleLayout_collapsedTextSize)) {
minTextSize = a.getDimensionPixelSize(
R.styleable.CollapsingTitleLayout_collapsedTextSize, 0);
setTextSize(TypedValue.COMPLEX_UNIT_PX, minTextSize);
} else {
// if not explicitly set then use the default text size as the min
minTextSize = getTextSize();
}
maxTextSize = a.getDimensionPixelSize(
R.styleable.CollapsingTitleLayout_maxExpandedTextSize, Integer.MAX_VALUE);
a.recycle();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
final float expandedTitleTextSize = Math.max(minTextSize,
ViewUtils.getSingleLineTextSize(getText().toString(), getPaint(),
w - getPaddingStart() - getPaddingEnd(),
minTextSize,
maxTextSize, 0.5f, getResources().getDisplayMetrics()));
setTextSize(TypedValue.COMPLEX_UNIT_PX, expandedTitleTextSize);
}
}
用到了attrs_collasping_title_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CollapsingTitleLayout">
<attr name="titleInset" format="reference|dimension" />
<attr name="titleInsetStart" format="reference|dimension" />
<attr name="titleInsetTop" format="reference|dimension" />
<attr name="titleInsetEnd" format="reference|dimension" />
<attr name="titleInsetBottom" format="reference|dimension" />
<attr name="maxExpandedTextSize" format="reference|dimension" />
<attr name="collapsedTextSize" format="reference|dimension" />
<attr name="lineHeightHint" />
<attr name="android:textAppearance" />
<attr name="android:maxLines" />
</declare-styleable>
<declare-styleable name="CollapsingTextAppearance">
<attr name="android:textSize" />
<attr name="android:textColor" />
<attr name="font" />
</declare-styleable>
</resources>
这个控件的目的,就是动态设置字体大小。
其余的都没什么好说的,使用CollapsingTitleLayout的属性有点偷懒,不过如果两个控件功能相似,也无伤大雅。新依赖了一个工具类ViewUtils,看看这个方法:
/**
* Recursive binary search to find the best size for the text.
*
* Adapted from https://github.com/grantland/android-autofittextview
*/
public static float getSingleLineTextSize(String text,
TextPaint paint,
float targetWidth,
float low,
float high,
float precision,
DisplayMetrics metrics) {
final float mid = (low + high) / 2.0f;
paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid, metrics));
final float maxLineWidth = paint.measureText(text);
if ((high - low) < precision) {
return low;
} else if (maxLineWidth > targetWidth) {
return getSingleLineTextSize(text, paint, targetWidth, low, mid, precision, metrics);
} else if (maxLineWidth < targetWidth) {
return getSingleLineTextSize(text, paint, targetWidth, mid, high, precision, metrics);
} else {
return mid;
}
}
这个方法就是尝试用二分法在min和max之间取得一个精度范围内的值来尽量把字都放在一行上。
经过试验,得到这个控件的表现:
- 当text字比较少的时候,其会尽量扩大至设置的maxTextSize来填满一行;
- 当text字比较多的时候,会尽量缩小字体来填满一行,直至达到minTextSize然后换行。
</a>
layout文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.branchmessenger.rxjavatestfield.MainActivity">
<com.branchmessenger.rxjavatestfield.widget.DynamicTypeTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Text"
app:maxExpandedTextSize = "100sp"
app:lineHeightHint="20sp" />
<com.branchmessenger.rxjavatestfield.widget.DynamicTypeTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Some Text Here Wow!"
app:maxExpandedTextSize = "100sp"
app:lineHeightHint="20sp" />
<com.branchmessenger.rxjavatestfield.widget.DynamicTypeTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="The answer to my conundrum was that..."
app:maxExpandedTextSize = "100sp"
app:lineHeightHint="20sp" />
<com.branchmessenger.rxjavatestfield.widget.DynamicTypeTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/long_text"
app:maxExpandedTextSize = "100sp"
app:lineHeightHint="20sp" />
</LinearLayout>
最大的“Text”的尺寸是100sp,这个我设置了普通的来对照过。我这里并没有设置minTextSize,最后textSize的大小是14sp也就是默认值。代码里面注释也说了假如没有特别指定minTextSize就是默认值。
小结
总算把几个TextView过了一遍。也不可能面面俱到,不过还是学到了很多东西。