專案地址:github.com/razerdp/Fri… (能弱弱的求個star或者fork麼QAQ)
前言
終於,這個系列的文章進入了全新的篇章,我們們的朋友圈系列進入研磨階段,目前我們可以成功展示資料,可以進行點贊或者取消點贊,可以進行評論。
但完成這些基本功能是不夠的。
一款產品之所以深入人心,是因為 “用著舒服”,說是“用著舒服”倒不如說是 “看著舒服”,而視覺互動,或者說互動動畫,正是一款App打動人的最重要的地方。
在之前的文章裡,我其實一直很注重這些小細節的,而這些小細節,也是微信現在所擁有的,或許你平常沒怎麼留意,但倘若取消掉這些動畫,相信你很快就會發覺 “這他喵的這麼生硬”
在本系列之前的文章裡,我們有留意到並實現了以下的動畫:
-
下拉重新整理時那個朋友圈的icon隨著listview滑動而滑動並自身旋轉
-
點贊時,popup的彈出動畫
-
點讚的時候,點讚的心心放大縮小動畫
-
評論時,當輸入法彈上來,輸入框自動對齊評論或者動態的底部
-
點選評論或者點贊時,對應的名字背景色改變
或許您想不到,一個小小的朋友圈看似簡單,卻有著這麼多小動畫。
好的,說了這麼多東西,除了總結外,實際上就是為了推銷文章←_←,我不知道是否真的有人會看,也許會有人僅僅是為了拿原始碼伸伸手而已。
但,我認為,跟同一圈子的人交流自己的思想,分享自己的經驗,不是一件很美妙的事情嗎?所以,即使沒什麼人看,我也會堅持把這個開源專案完成的以及在簡書堅持更新所有思路的-V-
正文
正如前言那一堆廢話所說,今天我們要實現的是這麼一個效果:
如您所見,當我們點選圖片的時候,你會發現,圖片會有一個灰色的蒙層疊加在上面,看起來就像是我們選中了圖片一樣。
如果要實現這個效果,按照我們的平時習慣,肯定是“selector走起”,奈何,當我們真的去試了一下之後,發現,貌似不管用啊0.0
於是百度一番,或者谷歌一番,發現又是設定clickable啊,又是selector什麼亂七八糟的順序問題啊。。。。
與其執著於這些,倒不如我們們自定義一個出來以應付一切的imageview。
關於Selector
如果硬要解釋這個東東,我想,我應該重新寫一篇文章來專門講解一下這個東東(事實上我也打算這麼做)
但在這裡我只會簡單的說說Selector到底是如何實現view的檢視變化的:
-
無論是什麼Selector,實際上最終都是Drawable,而Drawable可以理解為圖片,但如果需要更好的描述它,我覺得將其理解為ps可能會更好,因為Drawable是一個抽象類,它提供了**“something that can be drawn”**的方法,其實弄來弄去,都是draw()方法
-
Selector在java中具體化的說,其實就是StateListDrawable。
-
Drawable有一個mState陣列,它維護了不同狀態下的drawable,當view接收到touch事件,會呼叫refreshDrawableState來更新狀態,一般來說通過Drawable的isStateful()函式來得知是否與上次的狀態不同,如果是,則進行draw方法來改變view的檢視,在我們的眼中看起來就是顏色的改變。
上面很簡單的講述了selector的實現過程,從中我們不難得到以下資訊:
- 維護不同狀態對應的drawable的陣列
- 根據狀態是否改變來得到對應狀態的drawable
- draw方法
實現
梳理了一遍過程之後,我們只需要對症下藥就好了。
於是我們正式開工:
首先還是我的習慣,在自定義一個view之前,先配置attrs,在這裡我們就只配置一個屬性用來改變前景色。
attrs:
<!--ForceClickImageView-->
<declare-styleable name="ForceClickImageView">
<attr name="foregroundColor" format="reference|color"/>
</declare-styleable>
複製程式碼
然後新建一個ForceClickImageView類,繼承本專案的SuperImageView(實際上就是普通的imageview,封裝了glide的載入方法)
/**
* Created by 大燈泡 on 2016/4/11.
* 朋友圈的imageview,包含點選動作
*/
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);
}
}
複製程式碼
我們可以看到,在這裡我加了兩個成員,一個自然是drawable,另一個則是一個矩形,這個矩形主要是用來規定我們的drawable繪製的範圍,在這裡我們主要是用來快取這個view的範圍。
接下來在初始化方法裡面我們補充一下程式碼:
/**
* 初始化
*/
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允許傳入的引數除了reference外,還允許color
所以如果傳入的是一個selector,那麼getDrawable將會得到StateListDrawable
如果傳入的是color值,那麼getDrawable將會得到ColorDrawable,而ColorDrawable無論是什麼狀態,都只會有一個顏色。
所以如果傳入的是ColorDrawable,我們就需要手動new出一個StateListDrawable並設定我們不同狀態下的drawable了。
在上述程式碼中,如果我們得到的是ColorDrawable,我們就new出StateListDrawable,然後分別對應新增下述狀態和對應的drawable
- focused狀態 - 則是我們的前景色drawable
- pressed狀態 - 同上
- enable狀態 - 透明色的drawable
- 無狀態 - 同上
因為我們也不瞭解到底是會觸發哪種狀態,所以就直接扔了大概會觸發的狀態進去,因為在改變的時候,系統會從陣列中遍歷直到找出與狀態符合的drawable為止。
哦,不要忘了setCallback(this),因為View已經實現了該介面,所以我們把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,這個方法在view的狀態有發生改變的時候(比如從無焦點->有焦點),就會回撥。
在這裡,我們直接給呼叫 mForegroundDrawable.setState(getDrawableState());
因為getDrawableState()方法在view裡面已經是封裝好的了,它一共執行了兩個動作:
- 判斷上一次的drawable狀態,如果上一次的狀態不變,就返回上一次的
- 否則,執行onCreateDrawableState獲取符合的狀態
- 最終返回與drawablestate長度一致的int陣列
在setState之後,我們直接呼叫invalidate();要求這個view重新整理。
在draw方法裡面,父類的draw方法執行之後,也就是圖片展示之後,我們呼叫drawable的draw方法,此時就會將colorDrawable繪製到圖片的上層,表現起來就是有selector的效果了。
最後在xml佈局裡面新增我們的引數就可以了:
<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"