項(xiàng)目地址:https://github.com/razerdp/FriendCircle (能弱弱的求個(gè)star或者fork么QAQ)
《一起擼個(gè)朋友圈吧》 這是本文所處文集随珠,所有更新都會在這個(gè)文集里面哦帅刊,歡迎關(guān)注
上篇鏈接:http://www.reibang.com/p/4be8daaef3ca
下篇鏈接:http://www.reibang.com/p/4c5b5d7dc856
前言
終于码泞,這個(gè)系列的文章進(jìn)入了全新的篇章逛薇,咱們的朋友圈系列進(jìn)入研磨階段刹泄,目前我們可以成功展示數(shù)據(jù)外里,可以進(jìn)行點(diǎn)贊或者取消點(diǎn)贊,可以進(jìn)行評論特石。
但完成這些基本功能是不夠的盅蝗。
一款產(chǎn)品之所以深入人心,是因?yàn)?strong>“用著舒服”姆蘸,說是“用著舒服”倒不如說是“看著舒服”墩莫,而視覺交互,或者說交互動畫逞敷,正是一款A(yù)pp打動人的最重要的地方贼穆。
在之前的文章里,我其實(shí)一直很注重這些小細(xì)節(jié)的兰粉,而這些小細(xì)節(jié)故痊,也是微信現(xiàn)在所擁有的,或許你平常沒怎么留意玖姑,但倘若取消掉這些動畫愕秫,相信你很快就會發(fā)覺“這他喵的這么生硬”
在本系列之前的文章里,我們有留意到并實(shí)現(xiàn)了以下的動畫:
-
下拉刷新時(shí)那個(gè)朋友圈的icon隨著listview滑動而滑動并自身旋轉(zhuǎn)
-
點(diǎn)贊時(shí)焰络,popup的彈出動畫
-
點(diǎn)贊的時(shí)候戴甩,點(diǎn)贊的心心放大縮小動畫
-
評論時(shí),當(dāng)輸入法彈上來闪彼,輸入框自動對齊評論或者動態(tài)的底部
-
點(diǎn)擊評論或者點(diǎn)贊時(shí)甜孤,對應(yīng)的名字背景色改變
或許您想不到,一個(gè)小小的朋友圈看似簡單畏腕,卻有著這么多小動畫缴川。
好的,說了這么多東西描馅,除了總結(jié)外把夸,實(shí)際上就是為了推銷文章←_←,我不知道是否真的有人會看铭污,也許會有人僅僅是為了拿源碼伸伸手而已恋日。
但,我認(rèn)為嘹狞,跟同一圈子的人交流自己的思想岂膳,分享自己的經(jīng)驗(yàn),不是一件很美妙的事情嗎磅网?所以谈截,即使沒什么人看,我也會堅(jiān)持把這個(gè)開源項(xiàng)目完成的以及在簡書堅(jiān)持更新所有思路的-V-
正文
正如前言那一堆廢話所說,今天我們要實(shí)現(xiàn)的是這么一個(gè)效果:
如您所見速蕊,當(dāng)我們點(diǎn)擊圖片的時(shí)候,你會發(fā)現(xiàn)娘赴,圖片會有一個(gè)灰色的蒙層疊加在上面规哲,看起來就像是我們選中了圖片一樣。
如果要實(shí)現(xiàn)這個(gè)效果诽表,按照我們的平時(shí)習(xí)慣唉锌,肯定是“selector走起”,奈何竿奏,當(dāng)我們真的去試了一下之后袄简,發(fā)現(xiàn),貌似不管用啊0.0
于是百度一番泛啸,或者谷歌一番绿语,發(fā)現(xiàn)又是設(shè)置clickable啊,又是selector什么亂七八糟的順序問題啊候址。吕粹。。岗仑。
與其執(zhí)著于這些匹耕,倒不如咱們自定義一個(gè)出來以應(yīng)付一切的imageview。
關(guān)于Selector
如果硬要解釋這個(gè)東東荠雕,我想稳其,我應(yīng)該重新寫一篇文章來專門講解一下這個(gè)東東(事實(shí)上我也打算這么做)
但在這里我只會簡單的說說Selector到底是如何實(shí)現(xiàn)view的視圖變化的:
無論是什么Selector,實(shí)際上最終都是Drawable炸卑,而Drawable可以理解為圖片既鞠,但如果需要更好的描述它,我覺得將其理解為ps可能會更好矾兜,因?yàn)镈rawable是一個(gè)抽象類损趋,它提供了“something that can be drawn”的方法,其實(shí)弄來弄去椅寺,都是draw()方法
Selector在java中具體化的說,其實(shí)就是StateListDrawable蒋失。
Drawable有一個(gè)mState數(shù)組返帕,它維護(hù)了不同狀態(tài)下的drawable,當(dāng)view接收到touch事件篙挽,會調(diào)用refreshDrawableState來更新狀態(tài)荆萤,一般來說通過Drawable的isStateful()函數(shù)來得知是否與上次的狀態(tài)不同,如果是,則進(jìn)行draw方法來改變view的視圖链韭,在我們的眼中看起來就是顏色的改變偏竟。
上面很簡單的講述了selector的實(shí)現(xiàn)過程,從中我們不難得到以下信息:
- 維護(hù)不同狀態(tài)對應(yīng)的drawable的數(shù)組
- 根據(jù)狀態(tài)是否改變來得到對應(yīng)狀態(tài)的drawable
- draw方法
實(shí)現(xiàn)
梳理了一遍過程之后敞峭,我們只需要對癥下藥就好了踊谋。
于是我們正式開工:
首先還是我的習(xí)慣,在自定義一個(gè)view之前旋讹,先配置attrs殖蚕,在這里我們就只配置一個(gè)屬性用來改變前景色。
attrs:
<!--ForceClickImageView-->
<declare-styleable name="ForceClickImageView">
<attr name="foregroundColor" format="reference|color"/>
</declare-styleable>
然后新建一個(gè)ForceClickImageView類沉迹,繼承本項(xiàng)目的SuperImageView(實(shí)際上就是普通的imageview睦疫,封裝了glide的加載方法)
/**
* Created by 大燈泡 on 2016/4/11.
* 朋友圈的imageview,包含點(diǎn)擊動作
*/
public class ForceClickImageView extends SuperImageView {
//前景層
private Drawable mForegroundDrawable;
private Rect mCachedBounds = new Rect();
public ForceClickImageView(Context context) {
this(context, null);
}
public ForceClickImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ForceClickImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
}
我們可以看到鞭呕,在這里我加了兩個(gè)成員蛤育,一個(gè)自然是drawable,另一個(gè)則是一個(gè)矩形葫松,這個(gè)矩形主要是用來規(guī)定我們的drawable繪制的范圍缨伊,在這里我們主要是用來緩存這個(gè)view的范圍。
接下來在初始化方法里面我們補(bǔ)充一下代碼:
/**
* 初始化
*/
private void init(Context context, AttributeSet attrs) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForceClickImageView);
mForegroundDrawable = a.getDrawable(R.styleable.ForceClickImageView_foregroundColor);
if (mForegroundDrawable instanceof ColorDrawable) {
int foreGroundColor = a.getColor(R.styleable.ForceClickImageView_foregroundColor, 0x55c6c6c6);
mForegroundDrawable = new StateListDrawable();
ColorDrawable forceDrawable = new ColorDrawable(foreGroundColor);
ColorDrawable normalDrawable = new ColorDrawable(Color.TRANSPARENT);
((StateListDrawable) mForegroundDrawable).addState(new int[] { android.R.attr.state_focused },
forceDrawable);
((StateListDrawable) mForegroundDrawable).addState(new int[] { android.R.attr.state_pressed },
forceDrawable);
((StateListDrawable) mForegroundDrawable).addState(new int[] { android.R.attr.state_enabled },
normalDrawable);
((StateListDrawable) mForegroundDrawable).addState(new int[] {}, normalDrawable);
}
if (mForegroundDrawable != null) mForegroundDrawable.setCallback(this);
a.recycle();
}
首先我們獲取到我們定義的attrs屬性集进宝,然后得到drawable刻坊,在這里值得注意的是:
我們的attr允許傳入的參數(shù)除了reference外,還允許color
所以如果傳入的是一個(gè)selector党晋,那么getDrawable將會得到StateListDrawable
如果傳入的是color值谭胚,那么getDrawable將會得到ColorDrawable,而ColorDrawable無論是什么狀態(tài)未玻,都只會有一個(gè)顏色灾而。
所以如果傳入的是ColorDrawable,我們就需要手動new出一個(gè)StateListDrawable并設(shè)置我們不同狀態(tài)下的drawable了扳剿。
在上述代碼中旁趟,如果我們得到的是ColorDrawable,我們就new出StateListDrawable庇绽,然后分別對應(yīng)添加下述狀態(tài)和對應(yīng)的drawable
- focused狀態(tài) - 則是我們的前景色drawable
- pressed狀態(tài) - 同上
- enable狀態(tài) - 透明色的drawable
- 無狀態(tài) - 同上
因?yàn)槲覀円膊涣私獾降资菚|發(fā)哪種狀態(tài)锡搜,所以就直接扔了大概會觸發(fā)的狀態(tài)進(jìn)去,因?yàn)樵诟淖兊臅r(shí)候瞧掺,系統(tǒng)會從數(shù)組中遍歷直到找出與狀態(tài)符合的drawable為止耕餐。
哦,不要忘了setCallback(this)辟狈,因?yàn)閂iew已經(jīng)實(shí)現(xiàn)了該接口肠缔,所以我們把this傳入就好了夏跷。
最重要的部分完成后后,接下來就是一些方法的覆寫了明未。
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mForegroundDrawable != null && mForegroundDrawable.isStateful()) {
mForegroundDrawable.setState(getDrawableState());
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mForegroundDrawable != null) {
mForegroundDrawable.setBounds(mCachedBounds);
mForegroundDrawable.draw(canvas);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mForegroundDrawable != null) mCachedBounds.set(0, 0, w, h);
}
}
首先我們覆寫drawableStateChanged槽华,這個(gè)方法在view的狀態(tài)有發(fā)生改變的時(shí)候(比如從無焦點(diǎn)->有焦點(diǎn)),就會回調(diào)趟妥。
在這里猫态,我們直接給調(diào)用 mForegroundDrawable.setState(getDrawableState());
因?yàn)間etDrawableState()方法在view里面已經(jīng)是封裝好的了,它一共執(zhí)行了兩個(gè)動作:
- 判斷上一次的drawable狀態(tài)煮纵,如果上一次的狀態(tài)不變懂鸵,就返回上一次的
- 否則,執(zhí)行onCreateDrawableState獲取符合的狀態(tài)
- 最終返回與drawablestate長度一致的int數(shù)組
在setState之后行疏,我們直接調(diào)用invalidate();要求這個(gè)view刷新匆光。
在draw方法里面,父類的draw方法執(zhí)行之后酿联,也就是圖片展示之后终息,我們調(diào)用drawable的draw方法,此時(shí)就會將colorDrawable繪制到圖片的上層贞让,表現(xiàn)起來就是有selector的效果了周崭。
最后在xml布局里面添加我們的參數(shù)就可以了:
<razerdp.friendcircle.widget.imageview.ForceClickImageView
android:id="@+id/img"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:foregroundColor="@color/img_foregroundColor"
/>
其中img_foregroundColor的色值為:
"#85414141"