前言
如上圖所示,相信可愛的安卓程序猿們在開發(fā)中經(jīng)常會(huì)遇到這種樣式的UI開發(fā)。其實(shí)上面這種布局很簡單,沒有難度,只不過是繁雜的view嵌套而已。通常我們在實(shí)現(xiàn)上面這種效果的時(shí)候會(huì)有3種方式:
一層一層的搭建,首先外層是一個(gè)橫向的LinearLayout,然后里面包裹著四個(gè)LinearLayout作為子View, 每一個(gè)Linearlayout里面再寫上一個(gè)ImageView和一個(gè)TextView.如此簡單的一個(gè)布局我們竟然需要父View和子View一共13個(gè)View來實(shí)現(xiàn)。視圖的層級已經(jīng)達(dá)到了3層。這種方式笨重,低效,耗能。
繼承一個(gè)布局文件,實(shí)現(xiàn)自定義的tabView.這是自定義view中的一種。首先針對上圖中的一個(gè)tab寫一個(gè)布局文件abc.xml,很簡單,一個(gè)LinearLayout裝著一個(gè)ImageView和一個(gè)TextView,.然后對這個(gè)布局文件進(jìn)行封裝,添加自定義的屬性。這樣在實(shí)現(xiàn)上述布局時(shí)只要寫一個(gè)LinearLayout,里面添加4個(gè)TabView就好了。然而,這種方式看起來是簡單了。但實(shí)際上和方式一是沒有什么差別的,加載View時(shí),視圖層級依然是3層。 只是看起來簡單了而已。
使用TextView的drawableTop屬性。明明有這么方便優(yōu)雅的實(shí)現(xiàn)方式我們卻不用,太是暴殄天物了。于是乎,我們寫一個(gè)LinearLayout,里面添上4個(gè)TextView,在布局文件中為每一個(gè)TextView設(shè)置android:drawableTop="@drawable/haha.png"
然后呢,就沒然后了。已經(jīng)完成了!上述的那個(gè)布局樣式就這么輕松加愉快的實(shí)現(xiàn)了。視圖層級從原來得分3層變成了現(xiàn)在的兩層,不要小看這一層,在加載xml文件的時(shí)候,層級的增加會(huì)大大增加對資源和時(shí)間的消耗。其次,view個(gè)數(shù)從原來的13個(gè)變成了現(xiàn)在的5個(gè)。太棒了。
可是意外就像bug,總在你想不到的地方出現(xiàn)。這么完美的實(shí)現(xiàn)方式,到最后我們竟然無法設(shè)置TextView加載的drawable的大小!!也就是說資源文件本身寬高多大就只能多大。安卓沒有提供修改這個(gè)drawable大小的API.驚不驚喜,意不意外。
那么問題來了。我們到底能不能修改他的大小呢,答案當(dāng)然是能,這就需要我們通過繼承TextView來改造一下他的方法來實(shí)現(xiàn)。接下來我就向大家介紹一下我的思考過程和實(shí)現(xiàn)方式,一起看看每一步是否是合理的。
drawable大小的實(shí)現(xiàn)原理
首先當(dāng)然是閱讀源碼了,對此我們需要有一個(gè)突破口,這里我就從TextVIew的drawableTop屬性開始。我們在文件中設(shè)置了這個(gè)屬性,源碼中肯定要有相對應(yīng)的操作。在TextView的源碼里我們搜索drawableTop,
在TextView的構(gòu)造方法里系統(tǒng)獲取了drawableTop屬性,并復(fù)制給drawableTop變量。 源碼:
casecom.android.internal.R.styleable.TextView_drawableTop:drawableTop = a.getDrawable(attr);break;
我們查找DrawableTop變量。順著代碼往下一路走來,依然是在構(gòu)造方法里。當(dāng)獲取完了上下左右四個(gè)drawable后,系統(tǒng)執(zhí)行了下面這行代碼:
setCompoundDrawablesWithIntrinsicBounds(drawableLeft, drawableTop, drawableRight, drawableBottom);
顯而易見,這個(gè)方法對上下左右四個(gè)drawable做了處理。
進(jìn)入setCompoundDrawablesWithIntrinsicBounds方法:下面是系統(tǒng)的源碼,代碼不長,主要看四個(gè)if判斷, 其實(shí)就是為四個(gè)drawable分別設(shè)置各自的大小。
/** * 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);
}
這個(gè)方法很好理解,核心就是setBounds(0,0, top.getIntrinsicWidth(),top.getIntrinsicHeight());
這句話。到這里,就已經(jīng)很清晰了,系統(tǒng)獲取了我們?yōu)門extView設(shè)置的drawable,然后就根據(jù)drawable自身的大小來設(shè)置了要繪制時(shí)的邊界大小。所以我們在為TextVIew設(shè)置drawable時(shí),圖片是多大,就顯示多大,真是童叟無欺啊。只是苦了我們搬磚的,還得小心翼翼的找UI大大給切圖。
既然問題找到了。那解決就很容易了。我們實(shí)現(xiàn)一個(gè)自定義TextView,重寫setCompoundDrawablesWithIntrinsicBounds方法,在里面將setBound方法的傳值改為我們設(shè)置的大小就OK了。
自定義TextView----XXDrawableTextView
千里之行,始于足下,開始自定義XXDrawableTextView。
在style.xml文件中設(shè)置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"/>
這里我把上下左右四個(gè)為止的drawable都納入處理的范圍了,其實(shí)邏輯都一樣。
然后再添加下面這段:
<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方法,為各個(gè)drawable寶寶們設(shè)置寬度和高度。
@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);}
你看 ,其實(shí)最關(guān)鍵的還是setBound方法,將我們獲取到的寬高度傳了進(jìn)去。
到這,自定義View的基本工作已經(jīng)做完了,我們可以在布局文件中使用了,
注意 ,因?yàn)槭亲远xview,一定不要忘記在布局文件頭部添加
xmlns:app="http://schemas.android.com/apk/res-auto"哦。
最后寫一個(gè)LinearLayout,里面就替換成四個(gè)我們自定義的XXDrawableTextView,輕輕的為每一個(gè)XXDrawableTextView設(shè)置drawableHeight和drawableWidth屬性。就像下面這樣:
<com.xiaxiao.xiaoandroid.customview.XXDrawableTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="天氣不錯(cuò)"android:drawableTop="@drawable/tab2"app:drawableHeight_top="40dp"app:drawableWidth_top="40dp"android:gravity="center_horizontal"android:layout_weight="1"/>
靜悄悄的,簡潔的就像什么都沒發(fā)生一樣,然而一切卻變了,我們優(yōu)雅的實(shí)現(xiàn)了tab導(dǎo)航欄的效果,層級依然是2,view個(gè)數(shù)依然是最少的5個(gè)。App的運(yùn)行效率和性能就這么不經(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.
*
*用來解決文字和圖片組合時(shí)造成的view層級過多的問題。
*比如上圖標(biāo)下文字,下圖標(biāo)上文字,尤其是在實(shí)現(xiàn)一組tab均勻平鋪的效果時(shí)出現(xiàn)的大量view層級
*比如各app的底部欄,本類只要一層view既可。
*
*注意:必須設(shè)置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方法會(huì)首先在父類的構(gòu)造方法中執(zhí)行,
*彼時(shí)執(zhí)行時(shí)drawable的大小還都沒有開始獲取,都是0,
*這里獲取完自定義的寬高屬性后再次調(diào)用這個(gè)方法,插入drawable的大小
**/
setCompoundDrawablesWithIntrinsicBounds(
left,top,right,bottom);
}
/**
*SetstheDrawables(ifany)toappeartotheleftof,above,tothe
*rightof,andbelowthetext.Use{@codenull}ifyoudonotwanta
*Drawablethere.TheDrawables'boundswillbesettotheirintrinsic
*bounds.
*
*CallingthismethodwilloverwriteanyDrawablespreviouslysetusing
*{@link#setCompoundDrawablesRelative}orrelatedmethods.
*這里重寫這個(gè)方法,來設(shè)置上下左右的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);
}
/*
*代碼中動(dòng)態(tài)設(shè)置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的第二個(gè)參數(shù)是以多少度為開始點(diǎn),第三個(gè)參數(shù)-90度表示逆時(shí)針畫弧,正數(shù)表示順時(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();*/
}
}
其中注釋掉的是設(shè)置drawable為圓角的嘗試,可忽略。
我還添加了個(gè)修改寬高度的方法,可以運(yùn)行時(shí)在代碼中設(shè)置drawable的寬高。
其次還需要注意一下setCompoundDrawablesWithIntrinsicBounds方法的調(diào)用位置。
因?yàn)檫@個(gè)方法是在父類的構(gòu)造方法中調(diào)用的,也就是說當(dāng)執(zhí)行XXDrawableTextView的構(gòu)造方法時(shí),
首先會(huì)執(zhí)行父類的構(gòu)造方法,在執(zhí)行super方法時(shí),這個(gè)方法已經(jīng)進(jìn)行了。這時(shí)候getAttribute方法還沒調(diào)用呢,
也就是說各個(gè)寬高度屬性值都還沒獲得,所以需要在執(zhí)行完getArttribute方法后再調(diào)用一遍
setCompoundDrawablesWithIntrinsicBounds。
優(yōu)點(diǎn)呢,簡潔明了,就那么回事,缺點(diǎn)呢就是不能針對其中的drawable再做進(jìn)一步的處理了,比如設(shè)置成圓角之類。嘗試了一下自定義,發(fā)現(xiàn)太麻煩了。如果真的出現(xiàn)圖片設(shè)置成圓角的場景,
恐怕還得使用TextView加自定義的圓角ImageView。或者,找UI大大們了。
如果幫到你了,給個(gè)贊吧。
另:
簡書的編輯環(huán)境咋就這么差勁呢,代碼連個(gè)換行都不行,瞧這排版,醉了。
觀看友好版請移步:http://blog.csdn.net/xx23x/article/details/77997565