項(xiàng)目地址:https://github.com/razerdp/FriendCircle
一起擼個(gè)朋友圈吧這是本文所處文集藕甩,所有更新都會(huì)在這個(gè)文集里面哦侍匙,歡迎關(guān)注
上篇鏈接:http://www.reibang.com/p/1f85d3978bb5
下篇鏈接:http://www.reibang.com/p/26dd3aad965a
終于進(jìn)入自定義控件篇了待侵,不知道有沒有人興奮呢颗胡,反正在下很興奮舶担。-V-
本篇將會(huì)實(shí)現(xiàn)一個(gè)比較簡單的控件:點(diǎn)擊展開全文
嗯惭婿。找田。歌憨。大概效果圖是這樣的:
這個(gè)東東估計(jì)是整一個(gè)工程里最為簡單的一個(gè)控件了,當(dāng)然墩衙,網(wǎng)上也有很多例子务嫡,實(shí)現(xiàn)的都是類似的,本篇也都是一樣實(shí)現(xiàn)方法漆改。
在開始之前心铃,不妨來想想如何實(shí)現(xiàn)這個(gè)控件,就目前為止挫剑,在下想到了兩個(gè)方案:
- 繼承TextView去扣,通過行數(shù)判斷是否有全文,如果有樊破,則在原文另起一行愉棱,通過SpannableStringBuilder拼接原文+\n+包含點(diǎn)擊事件的ClickableSpan,然后動(dòng)態(tài)改變maxLines
- 通過繼承LinearLayout捶码,該layout包含2個(gè)textview羽氮,一個(gè)用來展示,一個(gè)用來點(diǎn)擊惫恼,通過行數(shù)判斷是否有全文档押,如果有,則點(diǎn)擊用的textview設(shè)為visible祈纯,否則設(shè)為gone令宿,點(diǎn)擊事件動(dòng)態(tài)改變展示用的textview的maxLines
經(jīng)過測試,第一個(gè)方法實(shí)現(xiàn)較難腕窥,主要是因?yàn)榱砥鹨恍械膯栴}粒没,因?yàn)樵瓉碚故镜臅r(shí)候已經(jīng)設(shè)置maxLines,如果要另起一行就意味著要增加maxLines簇爆,導(dǎo)致原文也展示了出來癞松,然后才是全文爽撒,因此不可取,遂采取方案2响蓉。
方案想完了硕勿,那么我們開工吧
按照我的習(xí)慣,肯定是先定義attrs枫甲,因?yàn)閺亩x屬性開始我可以大概設(shè)計(jì)一個(gè)雛形出來源武。
<declare-styleable name="ClickShowMoreLayout">
<attr name="show_line" format="integer"/>
<attr name="click_text" format="string"/>
<attr name="text_color" format="color"/>
<attr name="text_size" format="dimension"/>
</declare-styleable>
我們定義四個(gè)屬性,分別為:最多展示行數(shù)想幻,點(diǎn)擊展開的textview文字粱栖,展示文字的顏色以及展示文字的大小。
然后就是構(gòu)造器里面一大堆東東脏毯,這里就不貼代碼闹究,弄張圖吧
initView方法里面執(zhí)行的是兩個(gè)textview的初始化:
private void initView(Context context) {
mTextView=new TextView(context);
mClickToShow=new TextView(context);
mTextView.setTextSize(textSize);
mTextView.setTextColor(textColor);
mTextView.setMaxLines(showLine);
mClickToShow.setTextSize(textSize);
mClickToShow.setTextColor(getResources().getColor(R.color.nick));
mClickToShow.setText(clickText);
LinearLayout.LayoutParams params= new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.topMargin= UIHelper.dipToPx(context,10f);
mClickToShow.setLayoutParams(params);
mClickToShow.setOnClickListener(this);
setOrientation(VERTICAL);
addView(mTextView);
addView(mClickToShow);
}
值得注意的是,我們的layoutparams不可以通過getLayoutParams拿到哦抄沮,那是個(gè)空的跋核。
重頭戲是設(shè)置文字的方法和onClick方法:
public void setText(String str){
mTextView.setText(str);
mTextView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (!hasGetLineCount) {
hasMore = mTextView.getLineCount() > showLine;
hasGetLineCount=true;
}
mClickToShow.setVisibility(hasMore?VISIBLE:GONE);
mTextView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
}
@Override
public void onClick(View v) {
if (((TextView)v).getText().toString().equals(clickText)){
mTextView.setMaxLines(Integer.MAX_VALUE);
mClickToShow.setText("收起");
}else {
mTextView.setMaxLines(showLine);
mClickToShow.setText(clickText);
}
}
要判斷是否有更多內(nèi)容,我們只能獲取TextView繪制出來時(shí)文字的總行數(shù)叛买,那么文字都還沒繪制上去,我們應(yīng)該怎么拿到這個(gè)總行數(shù)呢蹋订,我們當(dāng)然不可以在onDraw里面拿率挣,于是我們就在onPreDraw里面拿,關(guān)于這里露戒,待會(huì)說說椒功。
拿到總行數(shù)后跟我們的最大顯示行數(shù)比較一下,然后得到是否有更多進(jìn)而設(shè)置點(diǎn)擊textview的可見性就完成了智什。
本篇完成动漾,下篇將會(huì)實(shí)現(xiàn)點(diǎn)贊展示的控件。
以下涉及官方源碼荠锭,稍微枯燥旱眯,可以跳過。
關(guān)于onPreDraw()证九,文檔是這么描述的:
Callback method to be invoked when the view tree is about to be drawn. At this point, all views in the tree have been measured and given a frame. Clients can use this to adjust their scroll bounds or even to request a new layout before drawing occurs.
(大概意思是【渣翻】:view樹在onDraw之前會(huì)回調(diào)這個(gè)方法删豺,此時(shí)view已經(jīng)進(jìn)行過measure【也就是可以拿到寬高】,在draw之前愧怜,我們可以進(jìn)行滑動(dòng)界限的調(diào)整【應(yīng)該是類似與ViewDragHelper那個(gè)邊界限定吧】甚至是重新部署)
而TextView關(guān)于文字的部署呀页,其實(shí)是與兩個(gè)layout有關(guān),一個(gè)是StaticLayout拥坛,一個(gè)是DynamicLayout這兩者的區(qū)別簡單來說就是setText時(shí)用的是普通的字符串還是包含有span的字符集合(另外還有BoringLayout 用于單行字符串的)蓬蝶〕痉郑【詳情請谷歌TextView渲染機(jī)制】
而既然textview實(shí)現(xiàn)了ViewTreeObserver.OnPreDrawListener這個(gè)接口,那么textview必定實(shí)現(xiàn)了這個(gè)方法回調(diào)丸氛,而且必定是有得到StaticLayout或者DynamicLayout培愁,否則我們得到的linecount只能為0.
于是我們打算找找是否實(shí)現(xiàn)了StaticLayout
我們可以查看TextView源碼:
在assumeLayout我們又可以找到這個(gè)方法
makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,physicalWidth, false);
其他參數(shù)先不管,我們先找到目標(biāo)
在這個(gè)方法里面我們繼續(xù)可以找到這個(gè)方法
在這個(gè)方法里面雪位,我們終于找到了我們需要的東西了:
可以看到竭钝,如果有span使用的是dynamiclayout,如果是singleline雹洗,則用boringlayout香罐,如果都沒有(即result==null),則用的是staticlayout
我們不妨繼續(xù)看看build里面的方法:
public StaticLayout build() {
StaticLayout result = new StaticLayout(this);
Builder.recycle(this);
return result;
}
而StaticLayout最終調(diào)用的是Layout的方法时肿,然而Layout的getLineCount是抽象方法庇茫,那么只能是StaticLayout實(shí)現(xiàn)這個(gè)方法了,經(jīng)過一直查找螃成,最終發(fā)現(xiàn)在out這個(gè)私有方法里面有關(guān)于mLineCount的計(jì)數(shù)
其實(shí)到這里旦签,我們可以大膽猜測控件或者其他什么元素的擺放是一行一行來放的,如果超過了寸宏,則另起一行直到放完為止宁炫。當(dāng)然,這僅僅是我的猜測氮凝,并沒有去查證羔巢。