前言
如上圖所示颓屑,相信可愛的安卓程序猿們在開發(fā)中經(jīng)常會遇到這種樣式的UI開發(fā)官脓。其實上面這種布局很簡單仿贬,沒有難度茁瘦,只不過是繁雜的view嵌套而已品抽。通常我們在實現(xiàn)上面這種效果的時候會有3種方式:
一層一層的搭建,首先外層是一個橫向的LinearLayout甜熔,然后里面包裹著四個LinearLayout作為子View桑包, 每一個Linearlayout里面再寫上一個ImageView和一個TextView.如此簡單的一個布局我們竟然需要父View和子View一共13個View來實現(xiàn)。視圖的層級已經(jīng)達到了3層纺非。這種方式笨重哑了,低效,耗能烧颖。
繼承一個布局文件弱左,實現(xiàn)自定義的tabView.這是自定義view中的一種。首先針對上圖中的一個tab寫一個布局文件abc.xml炕淮,很簡單拆火,一個LinearLayout裝著一個ImageView和一個TextView,.然后對這個布局文件進行封裝,添加自定義的屬性涂圆。這樣在實現(xiàn)上述布局時只要寫一個LinearLayout们镜,里面添加4個TabView就好了。然而润歉,這種方式看起來是簡單了模狭。但實際上和方式一是沒有什么差別的,加載View時踩衩,視圖層級依然是3層嚼鹉。 只是看起來簡單了而已贩汉。
使用TextView的drawableTop屬性。明明有這么方便優(yōu)雅的實現(xiàn)方式我們卻不用锚赤,太是暴殄天物了匹舞。于是乎,我們寫一個LinearLayout,里面添上4個TextView线脚,在布局文件中為每一個TextView設置android:drawableTop="@drawable/haha.png"
然后呢赐稽,就沒然后了。已經(jīng)完成了浑侥!上述的那個布局樣式就這么輕松加愉快的實現(xiàn)了又憨。視圖層級從原來得分3層變成了現(xiàn)在的兩層,不要小看這一層锭吨,在加載xml文件的時候蠢莺,層級的增加會大大增加對資源和時間的消耗。其次零如,view個數(shù)從原來的13個變成了現(xiàn)在的5個躏将。太棒了。
可是意外就像bug考蕾,總在你想不到的地方出現(xiàn)祸憋。這么完美的實現(xiàn)方式,到最后我們竟然無法設置TextView加載的drawable的大行の浴r强!也就是說資源文件本身寬高多大就只能多大塞帐。安卓沒有提供修改這個drawable大小的API.驚不驚喜拦赠,意不意外。
那么問題來了葵姥。我們到底能不能修改他的大小呢荷鼠,答案當然是能,這就需要我們通過繼承TextView來改造一下他的方法來實現(xiàn)榔幸。接下來我就向大家介紹一下我的思考過程和實現(xiàn)方式允乐,一起看看每一步是否是合理的。
drawable大小的實現(xiàn)原理
首先當然是閱讀源碼了削咆,對此我們需要有一個突破口牍疏,這里我就從TextVIew的drawableTop屬性開始。我們在文件中設置了這個屬性拨齐,源碼中肯定要有相對應的操作鳞陨。在TextView的源碼里我們搜索drawableTop,
在TextView的構造方法里系統(tǒng)獲取了drawableTop屬性,并復制給drawableTop變量奏黑。 源碼:
casecom.android.internal.R.styleable.TextView_drawableTop:drawableTop = a.getDrawable(attr);break;
我們查找DrawableTop變量炊邦。順著代碼往下一路走來编矾,依然是在構造方法里熟史。當獲取完了上下左右四個drawable后馁害,系統(tǒng)執(zhí)行了下面這行代碼:
setCompoundDrawablesWithIntrinsicBounds(drawableLeft, drawableTop, drawableRight, drawableBottom);
顯而易見,這個方法對上下左右四個drawable做了處理蹂匹。
進入setCompoundDrawablesWithIntrinsicBounds方法:下面是系統(tǒng)的源碼碘菜,代碼不長,主要看四個if判斷限寞, 其實就是為四個drawable分別設置各自的大小忍啸。
/** * Sets the Drawables (if any) to appear to the left of, above, to the * right of, and below the text. Use {@code null} if you do not want a * Drawable there. The Drawables' bounds will be set to their intrinsic * bounds. *
* Calling this method will overwrite any Drawables previously set using
* {@link #setCompoundDrawablesRelative} or related methods.
*
* @attr ref android.R.styleable#TextView_drawableLeft
* @attr ref android.R.styleable#TextView_drawableTop
* @attr ref android.R.styleable#TextView_drawableRight
* @attr ref android.R.styleable#TextView_drawableBottom
*/
@android.view.RemotableViewMethod
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);
}
這個方法很好理解,核心就是setBounds(0,0, top.getIntrinsicWidth(),top.getIntrinsicHeight());
這句話履植。到這里计雌,就已經(jīng)很清晰了,系統(tǒng)獲取了我們?yōu)門extView設置的drawable玫霎,然后就根據(jù)drawable自身的大小來設置了要繪制時的邊界大小凿滤。所以我們在為TextVIew設置drawable時,圖片是多大庶近,就顯示多大翁脆,真是童叟無欺啊。只是苦了我們搬磚的鼻种,還得小心翼翼的找UI大大給切圖反番。
既然問題找到了。那解決就很容易了叉钥。我們實現(xiàn)一個自定義TextView罢缸,重寫setCompoundDrawablesWithIntrinsicBounds方法,在里面將setBound方法的傳值改為我們設置的大小就OK了投队。
自定義TextView----XXDrawableTextView
千里之行祖能,始于足下,開始自定義XXDrawableTextView蛾洛。
在style.xml文件中設置XXDrawableTextView的屬性养铸,添加下面代碼:
<attrname="drawableWidth_left"format="dimension"/><attrname="drawableHeight_left"format="dimension"/><attrname="drawableWidth_top"format="dimension"/><attrname="drawableHeight_top"format="dimension"/><attrname="drawableWidth_right"format="dimension"/><attrname="drawableHeight_right"format="dimension"/><attrname="drawableWidth_bottom"format="dimension"/><attrname="drawableHeight_bottom"format="dimension"/>
這里我把上下左右四個為止的drawable都納入處理的范圍了,其實邏輯都一樣轧膘。
然后再添加下面這段:
<declare-styleable name="XXDrawableTextView"> <attrname="drawableWidth_left"/>
<attrname="drawableHeight_left"/>
<attrname="drawableWidth_top"/>
<attrname="drawableHeight_top"/>
<attrname="drawableWidth_right"/>
<attrname="drawableHeight_right"/>
<attrname="drawableWidth_bottom"/>
<attrname="drawableHeight_bottom"/>
</declare-styleable>
繼承TextView 钞螟,獲取自定義的drawable得寬高度屬性值。
*獲得我們所定義的自定義樣式屬性
*/TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.XXDrawableTextView, defStyleAttr,0);intn = a.getIndexCount();for(inti =0; i < n; i++){intattr = a.getIndex(i);switch(attr) {caseR.styleable.XXDrawableTextView_drawableWidth_left:leftDrawableWidth= a.getDimensionPixelSize(attr,10);break;caseR.styleable.XXDrawableTextView_drawableHeight_left:leftDrawableHeight= a.getDimensionPixelSize(attr,10);break;caseR.styleable.XXDrawableTextView_drawableWidth_top:topDrawableWidth= a.getDimensionPixelSize(attr,10);break;caseR.styleable.XXDrawableTextView_drawableHeight_top:topDrawableHeight= a.getDimensionPixelSize(attr,10);break;caseR.styleable.XXDrawableTextView_drawableWidth_right:rightDrawableWidth= a.getDimensionPixelSize(attr,10);break;caseR.styleable.XXDrawableTextView_drawableHeight_right:rightDrawableHeight= a.getDimensionPixelSize(attr,10);break;caseR.styleable.XXDrawableTextView_drawableWidth_bottom:bottomDrawableWidth= a.getDimensionPixelSize(attr,10);break;caseR.styleable.XXDrawableTextView_drawableHeight_bottom:bottomDrawableHeight= a.getDimensionPixelSize(attr,10);break; }}a.recycle();
重寫setCompoundDrawablesWithIntrinsicBounds方法谎碍,為各個drawable寶寶們設置寬度和高度鳞滨。
@Overridepublic voidsetCompoundDrawablesWithIntrinsicBounds(@NullableDrawable left,@NullableDrawable top,@NullableDrawable right,@NullableDrawable bottom) {this.left= left;this.top= top;this.right= right;this.bottom= bottom; System.out.println("啦啦啦啦啦啦啦");if(left !=null) { left.setBounds(0,0,leftDrawableWidth,leftDrawableHeight); }if(right !=null) { right.setBounds(0,0,rightDrawableWidth,rightDrawableHeight); }if(top !=null) { top.setBounds(0,0,topDrawableWidth,topDrawableHeight); }if(bottom !=null) { bottom.setBounds(0,0,bottomDrawableWidth,bottomDrawableHeight); } setCompoundDrawables(left, top, right, bottom);}
你看 ,其實最關鍵的還是setBound方法蟆淀,將我們獲取到的寬高度傳了進去拯啦。
到這澡匪,自定義View的基本工作已經(jīng)做完了,我們可以在布局文件中使用了褒链,
注意 唁情,因為是自定義view,一定不要忘記在布局文件頭部添加
xmlns:app="http://schemas.android.com/apk/res-auto"哦甫匹。
最后寫一個LinearLayout,里面就替換成四個我們自定義的XXDrawableTextView甸鸟,輕輕的為每一個XXDrawableTextView設置drawableHeight和drawableWidth屬性。就像下面這樣:
<com.xiaxiao.xiaoandroid.customview.XXDrawableTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="天氣不錯"android:drawableTop="@drawable/tab2"app:drawableHeight_top="40dp"app:drawableWidth_top="40dp"android:gravity="center_horizontal"android:layout_weight="1"/>
靜悄悄的兵迅,簡潔的就像什么都沒發(fā)生一樣抢韭,然而一切卻變了,我們優(yōu)雅的實現(xiàn)了tab導航欄的效果恍箭,層級依然是2刻恭,view個數(shù)依然是最少的5個。App的運行效率和性能就這么不經(jīng)意的被我們提高了那么一丟丟扯夭。
下面是具體的自定義XXDrawableTextView類:
XXDrawableTextView.java
[java]view plaincopy
packagecom.xiaxiao.xiaoandroid.customview;
importandroid.annotation.TargetApi;
importandroid.content.Context;
importandroid.content.res.TypedArray;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.Rect;
importandroid.graphics.drawable.Drawable;
importandroid.os.Build;
importandroid.support.annotation.Nullable;
importandroid.util.AttributeSet;
importandroid.widget.TextView;
importcom.xiaxiao.xiaoandroid.R;
/**
*Createdbyxiaxiaoon2017/9/13.
*
*用來解決文字和圖片組合時造成的view層級過多的問題鳍贾。
*比如上圖標下文字,下圖標上文字勉抓,尤其是在實現(xiàn)一組tab均勻平鋪的效果時出現(xiàn)的大量view層級
*比如各app的底部欄贾漏,本類只要一層view既可。
*
*注意:必須設置drawable的寬高度藕筋。
*
*/
publicclassXXDrawableTextViewextendsTextView{
publicfinalstaticintPOSITION_LEFT=0;
publicfinalstaticintPOSITION_TOP=1;
publicfinalstaticintPOSITION_RIGHT=2;
publicfinalstaticintPOSITION_BOTTOM=3;
intleftDrawableWidth=10;
intleftDrawableHeight=10;
inttopDrawableWidth=10;
inttopDrawableHeight=10;
intrightDrawableWidth=10;
intrightDrawableHeight=10;
intbottomDrawableWidth=10;
intbottomDrawableHeight=10;
PaintmPaint;
PaintmPaint2;
RectmBound;
Drawableleft;
Drawabletop;
Drawableright;
Drawablebottom;
publicXXDrawableTextView(Contextcontext){
this(context,null,0);
}
publicXXDrawableTextView(Contextcontext,AttributeSetattrs){
this(context,attrs,0);
}
publicXXDrawableTextView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
getAttributes(context,attrs,defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
publicXXDrawableTextView(Contextcontext,AttributeSetattrs,intdefStyleAttr,intdefStyleRes){
super(context,attrs,defStyleAttr,defStyleRes);
getAttributes(context,attrs,defStyleAttr);
}
publicvoidgetAttributes(Contextcontext,AttributeSetattrs,intdefStyleAttr){
/**
*獲得我們所定義的自定義樣式屬性
*/
TypedArraya=context.getTheme().obtainStyledAttributes(attrs,R.styleable.XXDrawableTextView,defStyleAttr,0);
intn=a.getIndexCount();
for(inti=0;i<n;i++)
{
intattr=a.getIndex(i);
switch(attr)
{
caseR.styleable.XXDrawableTextView_drawableWidth_left:
leftDrawableWidth=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_drawableHeight_left:
leftDrawableHeight=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_drawableWidth_top:
topDrawableWidth=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_drawableHeight_top:
topDrawableHeight=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_drawableWidth_right:
rightDrawableWidth=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_drawableHeight_right:
rightDrawableHeight=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_drawableWidth_bottom:
bottomDrawableWidth=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_drawableHeight_bottom:
bottomDrawableHeight=a.getDimensionPixelSize(attr,10);
break;
caseR.styleable.XXDrawableTextView_testnumber:
System.out.println("啦啦啦啦啦啦啦TextView2_testnumber:"+a.getDimensionPixelSize(attr,10));
break;
caseR.styleable.XXDrawableTextView_teststring:
System.out.println("啦啦啦啦啦啦啦TextView2_teststring:"+a.getString(attr));
}
}
a.recycle();
/*
*setCompoundDrawablesWithIntrinsicBounds方法會首先在父類的構造方法中執(zhí)行纵散,
*彼時執(zhí)行時drawable的大小還都沒有開始獲取,都是0,
*這里獲取完自定義的寬高屬性后再次調(diào)用這個方法隐圾,插入drawable的大小
**/
setCompoundDrawablesWithIntrinsicBounds(
left,top,right,bottom);
}
/**
*SetstheDrawables(ifany)toappeartotheleftof,above,tothe
*rightof,andbelowthetext.Use{@codenull}ifyoudonotwanta
*Drawablethere.TheDrawables'boundswillbesettotheirintrinsic
*bounds.
*
*CallingthismethodwilloverwriteanyDrawablespreviouslysetusing
*{@link#setCompoundDrawablesRelative}orrelatedmethods.
*這里重寫這個方法伍掀,來設置上下左右的drawable的大小
*
*@attrrefandroid.R.styleable#TextView_drawableLeft
*@attrrefandroid.R.styleable#TextView_drawableTop
*@attrrefandroid.R.styleable#TextView_drawableRight
*@attrrefandroid.R.styleable#TextView_drawableBottom
*/
@Override
publicvoidsetCompoundDrawablesWithIntrinsicBounds(@NullableDrawableleft,
@NullableDrawabletop,@NullableDrawableright,@NullableDrawablebottom){
this.left=left;
this.top=top;
this.right=right;
this.bottom=bottom;
System.out.println("啦啦啦啦啦啦啦");
if(left!=null){
left.setBounds(0,0,leftDrawableWidth,leftDrawableHeight);
}
if(right!=null){
right.setBounds(0,0,rightDrawableWidth,rightDrawableHeight);
}
if(top!=null){
top.setBounds(0,0,topDrawableWidth,topDrawableHeight);
}
if(bottom!=null){
bottom.setBounds(0,0,bottomDrawableWidth,bottomDrawableHeight);
}
setCompoundDrawables(left,top,right,bottom);
}
/*
*代碼中動態(tài)設置drawable的寬高度
**/
publicvoidsetDrawableSize(intwidth,intheight,intposition){
if(position==this.POSITION_LEFT){
leftDrawableWidth=width;
leftDrawableHeight=height;
}
if(position==this.POSITION_TOP){
topDrawableWidth=width;
topDrawableHeight=height;
}
if(position==this.POSITION_RIGHT){
rightDrawableWidth=width;
rightDrawableHeight=height;
}
if(position==this.POSITION_BOTTOM){
bottomDrawableWidth=width;
bottomDrawableHeight=height;
}
setCompoundDrawablesWithIntrinsicBounds(
left,top,right,bottom);
}
@Override
protectedvoidonDraw(Canvascanvas){
//Drawthebackgroundforthisview
super.onDraw(canvas);
/*
測試圓角的
Bitmapbitmap=Bitmap.createBitmap(getWidth(),getHeight(),Bitmap.Config.ARGB_8888);
Canvascanvas2=newCanvas(bitmap);
super.onDraw(canvas2);
mPaint=newPaint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
//16種狀態(tài)
mPaint.setXfermode(newPorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mPaint2=newPaint();
mPaint2.setColor(Color.YELLOW);
mPaint2.setXfermode(null);
intradius=100;
Pathpath=newPath();
path.moveTo(0,radius);
path.lineTo(0,0);
path.lineTo(radius,0);
//arcTo的第二個參數(shù)是以多少度為開始點,第三個參數(shù)-90度表示逆時針畫弧暇藏,正數(shù)表示順時針
path.arcTo(newRectF(0,0,radius*2,radius*2),-90,-90);
path.close();
canvas2.drawPath(path,mPaint);
canvas.drawBitmap(bitmap,0,0,mPaint2);
bitmap.recycle();*/
/*
finalintcompoundPaddingLeft=getCompoundPaddingLeft();
finalintcompoundPaddingTop=getCompoundPaddingTop();
finalintcompoundPaddingRight=getCompoundPaddingRight();
finalintcompoundPaddingBottom=getCompoundPaddingBottom();
finalintscrollX=getScrollX();
finalintscrollY=getScrollY();
finalintright=getRight();
finalintleft=getLeft();
finalintbottom=getBottom();
finalinttop=getTop();
finalintoffset=0;
finalintleftOffset=0;
finalintrightOffset=0;
*//*
*0-1-2-3
*left-top-right-bottom
**//*
Drawable[]drawables=getCompoundDrawables();
*//*
*Compound,notextended,becausetheiconisnotclipped
*ifthetextheightissmaller.
*//*
intvspace=bottom-top-compoundPaddingBottom-compoundPaddingTop;
inthspace=right-left-compoundPaddingRight-compoundPaddingLeft;
//IMPORTANT:ThecoordinatescomputedarealsousedininvalidateDrawable()
//MakesuretoupdateinvalidateDrawable()whenchangingthiscode.
if(drawables[0]!=null){
canvas.save();
canvas.translate(scrollX+getPaddingLeft()+leftOffset,
scrollY+compoundPaddingTop+
(vspace-leftDrawableHeight)/2);
drawables[0].draw(canvas);
canvas.restore();
}
//IMPORTANT:ThecoordinatescomputedarealsousedininvalidateDrawable()
//MakesuretoupdateinvalidateDrawable()whenchangingthiscode.
if(dr.mShowing[Drawables.RIGHT]!=null){
canvas.save();
canvas.translate(scrollX+right-left-mPaddingRight
-dr.mDrawableSizeRight-rightOffset,
scrollY+compoundPaddingTop+(vspace-dr.mDrawableHeightRight)/2);
dr.mShowing[Drawables.RIGHT].draw(canvas);
canvas.restore();
}
//IMPORTANT:ThecoordinatescomputedarealsousedininvalidateDrawable()
//MakesuretoupdateinvalidateDrawable()whenchangingthiscode.
if(dr.mShowing[Drawables.TOP]!=null){
canvas.save();
canvas.translate(scrollX+compoundPaddingLeft+
(hspace-dr.mDrawableWidthTop)/2,scrollY+mPaddingTop);
dr.mShowing[Drawables.TOP].draw(canvas);
canvas.restore();
}
//IMPORTANT:ThecoordinatescomputedarealsousedininvalidateDrawable()
//MakesuretoupdateinvalidateDrawable()whenchangingthiscode.
if(dr.mShowing[Drawables.BOTTOM]!=null){
canvas.save();
canvas.translate(scrollX+compoundPaddingLeft+
(hspace-dr.mDrawableWidthBottom)/2,
scrollY+bottom-top-mPaddingBottom-dr.mDrawableSizeBottom);
dr.mShowing[Drawables.BOTTOM].draw(canvas);
canvas.restore();
}
canvas.restore();*/
}
}
其中注釋掉的是設置drawable為圓角的嘗試使套,可忽略图焰。
我還添加了個修改寬高度的方法扇调,可以運行時在代碼中設置drawable的寬高玉锌。
其次還需要注意一下setCompoundDrawablesWithIntrinsicBounds方法的調(diào)用位置。
因為這個方法是在父類的構造方法中調(diào)用的瓮顽,也就是說當執(zhí)行XXDrawableTextView的構造方法時县好,
首先會執(zhí)行父類的構造方法,在執(zhí)行super方法時暖混,這個方法已經(jīng)進行了缕贡。這時候getAttribute方法還沒調(diào)用呢,
也就是說各個寬高度屬性值都還沒獲得,所以需要在執(zhí)行完getArttribute方法后再調(diào)用一遍
setCompoundDrawablesWithIntrinsicBounds晾咪。
優(yōu)點呢收擦,簡潔明了,就那么回事谍倦,缺點呢就是不能針對其中的drawable再做進一步的處理了塞赂,比如設置成圓角之類。嘗試了一下自定義剂跟,發(fā)現(xiàn)太麻煩了减途。如果真的出現(xiàn)圖片設置成圓角的場景酣藻,
恐怕還得使用TextView加自定義的圓角ImageView曹洽。或者辽剧,找UI大大們了送淆。
如果幫到你了,給個贊吧怕轿。
另:
簡書的編輯環(huán)境咋就這么差勁呢偷崩,代碼連個換行都不行,瞧這排版撞羽,醉了阐斜。
觀看友好版請移步:http://blog.csdn.net/xx23x/article/details/77997565