RecyclerView 首個(gè)Item頂部漸變效果
抖音“新鮮”Tab頁(yè)上有個(gè)效果比較有意思模狭,當(dāng)Item滑到頂部的時(shí)候嵌屎,item上的文字信息會(huì)有一個(gè)漸變的隱藏效果,如下圖
這個(gè)效果實(shí)現(xiàn)起來(lái)不是特別難,但是有一些細(xì)節(jié)地方需要我們考慮一下揽涮,下面我就把我實(shí)現(xiàn)的方式分享一下猴娩,先來(lái)一張效果圖
實(shí)現(xiàn)方式
- 使用RecyclerView.OnScrollListener監(jiān)聽RecyclerView的滾動(dòng)
- 在onScrolled(RecyclerView recyclerView, int dx, int dy)方法中獲取第一個(gè)Item
- 獲取第一個(gè)Item的滑動(dòng)出屏幕的百分比
- 根據(jù)計(jì)算獲取當(dāng)前文字區(qū)域的Alpha值
代碼如下:
RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
//這里我使用的是GridLayoutManager,并且RecyclerView只有兩列
if(manager instanceof GridLayoutManager){
GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
int firstVisiblePos = gridLayoutManager.findFirstVisibleItemPosition();
setViewAplha(gridLayoutManager.findViewByPosition(firstVisiblePos));
setViewAplha(gridLayoutManager.findViewByPosition(firstVisiblePos+1));
int firstCompletelyVisible = gridLayoutManager.findFirstCompletelyVisibleItemPosition();
ItemView view1 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible);
ItemView view2 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible + 1);
if(view1 != null){
view1.setAlpha(1);
}
if(view2 != null){
view2.setAlpha(1);
}
}
}
};
float beginPercent = 0.2f;
float endValue = 2;
private void setViewAplha(View view){
if (view == null || !(view instanceof ItemView))
return;
float p = UIUtils.px2dip(Math.abs((int) view.getY())) * 1.0f / UIUtils.px2dip(view.getHeight()) * 1.0f;
float curPercent = Float.compare(p - beginPercent, 0.0f) < 0 ? 0.0f : p - beginPercent;
curPercent = Float.compare(1, curPercent * endValue) < 0 ? 1 : curPercent * endValue;
view.setAlpha(1 - curPercent);
}
這里的ItemView是自定義的每一個(gè)內(nèi)容區(qū)域的View冗尤,布局比較簡(jiǎn)單 只有一個(gè)TextView 代碼如下
private static class ItemView extends LinearLayout{
TextView textView;
Context mContext;
public ItemView(Context context) {
super(context);
mContext = context;
init();
}
private void init(){
View root = LayoutInflater.from(mContext).inflate(R.layout.layout_test_item,this);
textView = (TextView) root.findViewById(R.id.item_text);
}
public void setText(String text){
textView.setText(text);
}
public void setAlpha(float alpha){
textView.setAlpha(alpha);
}
}
需要考慮的細(xì)節(jié)問(wèn)題
- Adapter Holder的復(fù)用
我們都知道RecyclerView.Adapter會(huì)對(duì)Holder進(jìn)行復(fù)用來(lái)節(jié)約內(nèi)存的開銷,如果假設(shè)一屏有十個(gè)item胀溺,當(dāng)我們滑動(dòng)到第十一個(gè)Item時(shí)會(huì)復(fù)用第一個(gè)Item的Holder裂七,所以在onBindViewHolder方法中我們需要把文字區(qū)域的Alpha值設(shè)置為不透明,adaper的代碼如下:
RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemView itemView = new ItemView(parent.getContext());
ViewHolder holder = new ViewHolder(itemView);
return holder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ViewHolder holder1 = (ViewHolder) holder;
holder1.itemView.setText("我是Item"+position);
//需要把item設(shè)置不透明仓坞,否則可能會(huì)因?yàn)閺?fù)用導(dǎo)致item剛顯示就是看不見的
holder1.itemView.setAlpha(1);
}
@Override
public int getItemCount() {
return 40;
}
class ViewHolder extends RecyclerView.ViewHolder{
ItemView itemView;
public ViewHolder(View itemView) {
super(itemView);
this.itemView = (ItemView) itemView;
}
}
};
- 快速滑動(dòng)的問(wèn)題
RecyclerView 滑動(dòng)時(shí)會(huì)有如下三種狀態(tài)
/**
* The RecyclerView is not currently scrolling.
* @see #getScrollState()
*/
public static final int SCROLL_STATE_IDLE = 0;
/**
* The RecyclerView is currently being dragged by outside input such as user touch input.
* @see #getScrollState()
*/
public static final int SCROLL_STATE_DRAGGING = 1;
/**
* The RecyclerView is currently animating to a final position while not under
* outside control.
* @see #getScrollState()
*/
public static final int SCROLL_STATE_SETTLING = 2;
當(dāng)我們快速滑動(dòng)時(shí)可能會(huì)由于滑動(dòng)速度過(guò)快背零,導(dǎo)致在下次回調(diào)時(shí)可能已經(jīng)是下一個(gè)item元素,例如无埃,在當(dāng)前onScrolled方法回調(diào)時(shí)徙瓶,findFirstVisibleItemPosition可能是item4,然后我們計(jì)算出文字區(qū)域的Alpha是0.6嫉称,因?yàn)榛瑒?dòng)過(guò)快侦镇,下次回調(diào)onScrolled方法時(shí),findFirstVisibleItemPosition可能就變成了item6织阅,Alpha值是0.8壳繁,因?yàn)槲覀內(nèi)鄙倭薸tem4的完整狀態(tài),就導(dǎo)致了當(dāng)快速滑動(dòng)停止時(shí),可能item6是正確的透明度闹炉,但是我們希望item4是完全不透明的蒿赢,但是實(shí)際卻是0.6
為了解決這個(gè)問(wèn)題,我嘗試了幾種方法渣触,像監(jiān)聽滑動(dòng)狀態(tài)啊羡棵、判斷onScrolled中的dy參數(shù)啊,后來(lái)我發(fā)現(xiàn)我有點(diǎn)給想的麻煩了嗅钻,其實(shí)不管滑動(dòng)速度多快皂冰,每個(gè)item在滑動(dòng)到第一個(gè)完全可展示的位置時(shí),是一定會(huì)被我們知道的养篓,比如雖然我們不能拿到item4從完全透明到完全不透明的所有狀態(tài)灼擂,但是當(dāng)item6是第一個(gè)item時(shí),item4一定是第一可以完全展示的item(我的RecyclerView有兩列)觉至,所以我們就可以通過(guò)這個(gè)點(diǎn)來(lái)下手剔应,通過(guò)LayoutManager的findFirstCompletelyVisibleItemPosition來(lái)找到第一個(gè)完全可見的Item位置,具體體現(xiàn)就是剛才RecyclerView.OnScrollListener 里面的代碼
int firstCompletelyVisible = gridLayoutManager.findFirstCompletelyVisibleItemPosition();
ItemView view1 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible);
ItemView view2 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible + 1);
if(view1 != null){
view1.setAlpha(1);
}
if(view2 != null){
view2.setAlpha(1);
}
- Alpha值的計(jì)算
這里還有一個(gè)要考慮的問(wèn)題语御,就是Alpha的取值問(wèn)題峻贮,仔細(xì)看抖音中的效果,并不是item剛開始滑動(dòng)就開始改變透明度应闯,而是大概Item滑出屏幕1/5左右開始纤控,這里就需要我們做一點(diǎn)點(diǎn)小小的計(jì)算,當(dāng)然我的計(jì)算方法可能有點(diǎn)low碉纺,計(jì)算方法就是剛才的setViewAplha里面所做的船万,在這在粘貼一下
float beginPercent = 0.2f;
float endValue = 2;
private void setViewAplha(View view){
if (view == null || !(view instanceof ItemView))
return;
float p = UIUtils.px2dip(Math.abs((int) view.getY())) * 1.0f / UIUtils.px2dip(view.getHeight()) * 1.0f;
float curPercent = Float.compare(p - beginPercent, 0.0f) < 0 ? 0.0f : p - beginPercent;
curPercent = Float.compare(1, curPercent * endValue) < 0 ? 1 : curPercent * endValue;
view.setAlpha(1 - curPercent);
}
這個(gè)beginPercent變量表示我想要在Item滑出屏幕多少時(shí),才開始改變Alpha值骨田,這里我定義成了0.2也就是1/5耿导,然后在第一次計(jì)算curPercent時(shí)
float p = UIUtils.px2dip(Math.abs((int) view.getY())) * 1.0f / UIUtils.px2dip(view.getHeight()) * 1.0f;
float curPercent = Float.compare(p - beginPercent, 0.0f) < 0 ? 0.0f : p - beginPercent;
可見如果當(dāng)p<0.2時(shí),我的curPercent會(huì)等于0态贤,后續(xù)在做什么計(jì)算都不會(huì)改變文字區(qū)域的Alpha值
現(xiàn)在已經(jīng)可以設(shè)置從item滑動(dòng)到多少才開始出現(xiàn)漸變的效果了舱呻,那么下一步我們就應(yīng)該定義當(dāng)item滑動(dòng)到什么位置時(shí)文字區(qū)域完全透明也就是Alpha值等于0,這時(shí)候endValue的作用就來(lái)了悠汽,我們可以通過(guò)給curPercent乘一個(gè)值來(lái)讓它達(dá)到指定位置時(shí)可以變成1箱吕,然后1-curPercent=0
curPercent = Float.compare(1, curPercent * endValue) < 0 ? 1 : curPercent * endValue;
這里我的endValue=2,可以在item滑出屏幕70%時(shí)柿冲,文字區(qū)域完全透明,我的計(jì)算方法如下表格
curPercent | 與0.2的差值(滑動(dòng)從1/5開始計(jì)算) |
---|---|
0.0 | - |
0.1 | - |
0.2 | - |
0.3 | 0.1 |
0.4 | 0.2 |
0.5 | 0.3 |
0.6 | 0.4 |
0.7 | 0.5 |
0.8 | 0.6 |
0.9 | 0.7 |
所以如果我想讓Item滑動(dòng)到70%時(shí)完全透明假抄,我只要設(shè)置endValue是2既可怎栽,因?yàn)榇藭r(shí)curPercent是0.7-0.2=0.5 然后乘2剛好等于1,之前一直都會(huì)小于1婚瓜,這樣的話刑棵,我們可以隨意設(shè)定滑動(dòng)漸變從哪開始到哪結(jié)束巴刻,比如我現(xiàn)在希望item滑出屏幕30%開始 同樣在item滑出屏幕70%完全透明那么根據(jù)下面這個(gè)表格
curPercent | 與0.3的差值(滑動(dòng)從30%開始計(jì)算) |
---|---|
0.0 | - |
0.1 | - |
0.2 | - |
0.3 | - |
0.4 | 0.1 |
0.5 | 0.2 |
0.6 | 0.3 |
0.7 | 0.4 |
0.8 | 0.5 |
0.9 | 0.6 |
我們可以計(jì)算出 endValue=3( (0.7-0.3)* endValue =1.2 )即可保證在滑動(dòng)到70%時(shí) 完全的透明