TextView以及其子类 先说两个小知识
1.TextView 实现跑马灯效果
1 2 3 4 5 6 <TextView android:singleLine ="true" // 必须 android:marqueeRepeatLimit ="marquee_forever" //是否永远循环 android:focusable ="true" // 必须 android:focusableInTouchMode ="true" // 必须 android:ellipsize ="marquee" "/> // 必须
2.TextView 的字体设置 字体设置可以在 fonts.xml 文件中查找,其定义如下
1 2 3 4 5 6 7 8 9 <family > <nameset > <name > cursive</name > </nameset > <fileset > <file > DancingScript-Regular.ttf</file > <file > DancingScript-Bold.ttf</file > </fileset > </family >
使用时指定名称就可以了
1 app:family="casual" //这是一个 string 类型的属性
读取该值后可以创建字体
1 Typeface.create(mFamily, Typeface.NORMAL)
TextView的绘制:Layout TextView 在绘制时会先绘制四边的 drawable 。
1 2 3 4 android:drawableLeft="@drawable/left" android:drawableTop="@drawable/top" android:drawableBottom="@drawable/bottom" android:drawableRight="@drawable/right"
其余的绘制工作就交给 Layout 类了,正常情况下采用 StaticLayout,而 EditText 采用 DynamicLayout。
Layout 负责文本的布局和绘制,绘制效果取决于构造它的诸多参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected Layout (CharSequence text, TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingMult, float spacingAdd)
绘制方法包括两步,即按行绘制文本即行背景。
Layout 有很多获取和设置布局的信息,如
1.获得某画笔类TextPaint下的文本宽度
1 public static float getDesiredWidth (CharSequence source, int start, int end, TextPaint paint)
2.获得文本行数以及某一行的信息
1 2 3 4 public int getLineCount () ;public int getLineTop (int line) ;public int getLineDescent (int line) ;public abstract int getLineStart (int line) ;
实际上完全可以覆盖掉默认的绘制方法,利用Layout实现绘制效果,如
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected void onDraw (Canvas canvas) { Layout layout = getLayout(); int count = layout.getLineCount(); for (int i = 0 ; i < count; i++) { float lineLeft = layout.getLineLeft(i); float lineBaseline = layout.getLineBaseline(i); String lineText = getText().subSequence(lineStart, lineEnd).toString(); canvas.drawText(lineText, lineLeft, lineBaseline, getPaint()); } }
这个效果和默认的绘制效果至少看上去完全一致(实际丢失了对marquee标记的处理),使用 Layout 还能够达成更多效果。
EditText EditText 实际上完完全全就是 TextView,只不过它采用的样式如下
1 2 3 4 5 6 7 8 9 10 11 <style name ="Widget.EditText" > <item name ="focusable" > true</item > <item name ="focusableInTouchMode" > true</item > <item name ="clickable" > true</item > <item name ="background" > ?attr/editTextBackground</item > <item name ="textAppearance" > ?attr/textAppearanceMediumInverse</item > <item name ="textColor" > ?attr/editTextColor</item > <item name ="gravity" > center_vertical</item > <item name ="breakStrategy" > simple</item > <item name ="hyphenationFrequency" > normal</item > </style >
其背景 drawable 在正常状态下是白色的.9图(@drawable/textfield_default),通过设置如下属性 TextView 亦可以选择文本供剪贴板使用
1 android:textIsSelectable="true"
TextView 和 EditText 所得的文本为 EditText 类,这是一个继承了很多文本处理接口的类,功能强大,例如可以设置过滤器 InputFilter,数字输入框,号码框就是靠其实现的。
AutoCompleteTextView 继承 EditText ,本是一个带 PopupWindow(带 ListView) 的 EditText,获取焦点时显示 PopupWindow。
Button 实际上也完完全全就是 TextView,只不过它使用了默认的样式,改变了外观,所采用的样式可以在 themes 文件中找到,如下
1 2 3 4 5 6 7 8 <style name ="Widget.Button" > <item name ="background" > @drawable/btn_default</item > <item name ="focusable" > true</item > <item name ="clickable" > true</item > <item name ="textAppearance" > ?attr/textAppearanceSmallInverse</item > <item name ="textColor" > @color/primary_text_light</item > <item name ="gravity" > center_vertical|center_horizontal</item > </style >
其中影响按钮外观最大的就是背景 Drawable,默认情况下为一个 SelectDrawable,在正常状态下是灰色的.9图(位于res\drawable-mdpi.btn_default_normal.9.png),并在不同的状态/主题下采用不同颜色的.9图。
android 默认提供了很多按钮的 style ,如
1 style="?android:attr/imageButtonStyle"
CompoundButton 是一种特殊的 Button,从功能上将它只有两种状态,即是否 isChecked;从实现上看,它采用了新 Drawable 来表示 checked 状态,并重写了 onDraw 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); buttonDrawable.setBounds(left, top, right, bottom); if (buttonDrawable != null ) { final int scrollX = mScrollX; final int scrollY = mScrollY; if (scrollX == 0 && scrollY == 0 ) { buttonDrawable.draw(canvas); } else { canvas.translate(scrollX, scrollY); buttonDrawable.draw(canvas); canvas.translate(-scrollX, -scrollY); } } }
因此我们知道改变 CompoundButton 既可以改变其外观,在XML上属性为
1 android:button="@null" // 即buttonDrawable为空
在 java 代码上
1 public void setButtonDrawable (@DrawableRes int resId)
子类 CheckBox玩玩全全就是 CompoundButton,另一个子类 Switch 则较为复杂。
发生点击事件后处理如下
1 2 3 4 5 6 7 8 @Override public boolean performClick () { toggle(); return super .performClick(); } public void toggle () { setChecked(!mChecked); }
Switch Switch 上除了 button 参数,另有两个 drawable,底层的叫 “track”,上层的叫“thumb”,上层面积是底层的一半左右,通过遮盖和移位来表示 checked 状态。此外 checked 时的文本叫做 “textOn”,而反之叫做 “textOff”。
注意此时 Button Drawable 依然有效。
在实现上,除了重写 onDraw 方法外,还要处理 “thumb” 区域的触摸事件,以完成状态切换。
因为“thumb” 是飘在 “track”上左右移动的,因此切换的动画是用属性动画完成的。
一些与 Text 相关的库 HTextView 这个库实现的效果华丽,代码的风格和扩展性也非常好,而且作者将不同的效果仅仅分包,引入某种效果的体积极小,实在是不可多得的优质库。
1 2 3 4 public abstract class HTextView extends TextView { public abstract void setProgress (float progress) ; public abstract void animateText (CharSequence text) ; }
1.Line 效果实质是在文本之外定点,描边,画线,这是附加性质的绘制。
2.Fade 效果是自发绘制文本,这里要利用 TextView 中 Layout 这个类。首先**均匀的挑出透明的字符位置,组成 alphaList。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override protected void drawFrame (Canvas canvas) { Layout layout = mHTextView.getLayout(); int gapIndex = 0 ; for (int i = 0 ; i < layout.getLineCount(); i++) { int lineStart = layout.getLineStart(i); int lineEnd = layout.getLineEnd(i); float lineLeft = layout.getLineLeft(i); float lineBaseline = layout.getLineBaseline(i); String lineText = mText.subSequence(lineStart, lineEnd).toString(); for (int c = 0 ; c < lineText.length(); c++) { int alpha = alphaList.get(gapIndex); mPaint.setAlpha((int ) ((255 - alpha) * progress + alpha)); String str = lineText.substring(j, j+1 ); canvas.drawText(str, lineLeft, lineBaseline, mPaint); lineLeft += getPaint().measureText(str); } }
这里要准确测量每一个字符的宽度。
3.打字机效果的实现十分简单
1 2 3 public void setProgress (float progress) { setText(mText.subSequence(0 , (int ) (mText.length() * progress))); }
4.彩虹效果:通过操作Shader的矩阵完成染色。
1 2 3 4 5 6 7 8 9 10 protected void onDraw (Canvas canvas) { if (mMatrix == null ) { mMatrix = new Matrix(); } mTranslate += colorSpeed; mMatrix.setTranslate(mTranslate, 0 ); mLinearGradient.setLocalMatrix(mMatrix); super .onDraw(canvas); postInvalidateDelayed(100 ); }
5.Scale 效果:利用 Layout 重新定义了绘制方法,以重绘新旧字符串。重复的字符移位,其余的收缩。以下两种情况亦然。
一般情况下闪光效果和 HTextView 一样是采用渐变的效果达成的,对画笔进行设置,使渐变的位置发生偏移,即产生闪光效果。这里只需要改变 Paint 取像素的方式即可
1 2 mGradient.setLocalMatrix(mMatrix); getPaint().setShader(mGradient);
这里将原生画笔的像素提供方式改变了,它只与绘制的内容文本有关,与背景没有干扰。如果要取消闪光效果,可以将其设为null。
但要达到较好的效果还需要
1.仔细的设置渐变的参数,渐变的设置如下
1 2 3 4 5 6 7 mGradient = new LinearGradient(getWidth(), 0 , 0 , 0 , new int []{ getCurrentTextColor(), 0xFFFFFFFF , getCurrentTextColor()}, new float []{0 , 0.5f , 1 }, Shader.TileMode.CLAMP);
2.处理矩阵的变化已达到动画效果。
1 2 3 mMatrix.setTranslate((mProcess-0.5f )*getMeasuredWidth(), 0 ); mMGradient.setLocalMatrix(mMatrix); super .onDraw(canvas);
facebook的这个 Shimmer 库可贵的是将闪光效果扩展到布局上去了,其实他的做法也是通过Shader,但首先要将 布局内容转为 Bitmap,再在绘制 Bitmap 的时候应用上述这一套。
这里文本设置两种模式:显示完全和保留显示(最多显示240字);
ReadMoreTextView 是利用 ClickSpan 来设置最后的提示文本,ExpandableTextView 这个库则是另外的思路,它本质上是一个 Linear 布局,需要重新计算尺寸,以及在点击事件发生时播放动画。相比之下前者的实现更简洁,但在切换时没有实现动画效果。
实现这个效果,我们要重写如下方法
1 2 3 4 5 @Override public void setText (CharSequence text, BufferType type) { super .setText(getDisplayText(text), type); setMovementMethod(LinkMovementMethod.getInstance()); }
使用 SpannableStringBuilder 来改造原来的 text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private CharSequence getDisplayText (final CharSequence text) { int len = text.length(); if (len < 240 ) { return text; } SpannableStringBuilder builder = generateText(text, isShowMore); builder.setSpan(new ClickableSpan() { @Override public void onClick (View widget) { isShowMore = !isShowMore; setText(text); } }, start, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE); } ; return builder; }
1 <TextView fontPath ="fonts/MyFont.ttf" />
这里 TextView 可以使用新的标签,这是通过代理 Context 来实现的,这里主要是改造 LayoutInflater 。改造 Factory2 和 Factory完成。