viewpager 自定义翻页效果_Android RecyclerView自定义LayoutManager
在第一篇中已經(jīng)講過,LayoutManager主要用于布局其中的Item,在LayoutManager中能夠?qū)γ總€(gè)Item的大小,位置進(jìn)行更改,將它放在我們想要的位置,在很多優(yōu)秀的效果中,都是通過自定義LayoutManager來實(shí)現(xiàn)的,比如:
Github: https://github.com/dongjunkun/RecyclerViewAccentFirst
可以看到效果非常棒,通過這一節(jié)的學(xué)習(xí),大家也就理解了自定義LayoutManager的方法,然后再理解這些控件的代碼就不再難了。
在這節(jié)中,我們先自己制作一個(gè)LinearLayoutManager,來看下如何自定義LayoutManager,下節(jié)中,我們會(huì)通過自定義LayoutManager來制作第一個(gè)滾輪翻頁(yè)的效果。
自定義CustomLayoutManager
先生成一個(gè)類CustomLayoutManager,派生自LayoutManager:
public?class?CustomLayoutManager?extends?LayoutManager?{????@Override
????public?LayoutParams?generateDefaultLayoutParams()?{
????????return?null;
????}
}
當(dāng)我們派生自LayoutManager時(shí),會(huì)強(qiáng)制讓我們生成一個(gè)方法generateDefaultLayoutParams。這個(gè)方法就是RecyclerView Item的布局參數(shù),換種說法,就是RecyclerView 子 item 的 LayoutParameters,若是想修改子Item的布局參數(shù)(比如:寬/高/margin/padding等等),那么可以在該方法內(nèi)進(jìn)行設(shè)置。一般來說,沒什么特殊需求的話,則可以直接讓子item自己決定自己的寬高即可(wrap_content)。
public?class?CustomLayoutManager?extends?LayoutManager?{????@Override
????public?LayoutParams?generateDefaultLayoutParams()?{
????????return?new?RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
????????????????RecyclerView.LayoutParams.WRAP_CONTENT);
????}
}
如果這時(shí)候,我們把上節(jié)demo中LinearLayoutManager替換下:
public?class?LinearActivity?extends?AppCompatActivity?{????private?ArrayList?mDatas?=?new?ArrayList<>();@Overrideprotected?void?onCreate(Bundle?savedInstanceState)?{super.onCreate(savedInstanceState);
????????setContentView(R.layout.activity_linear);
????????…………
????????RecyclerView?mRecyclerView?=?(RecyclerView)?findViewById(R.id.linear_recycler_view);
????????mRecyclerView.setLayoutManager(new?CustomLayoutManager());
????????RecyclerAdapter?adapter?=?new?RecyclerAdapter(this,?mDatas);
????????mRecyclerView.setAdapter(adapter);
????}
????…………
}
運(yùn)行一下,發(fā)現(xiàn)頁(yè)面完全空白:
我們說過所有的Item的布局都是在LayoutManager中處理的,很明顯,我們目前在CustomLayoutManager中并沒有布局任何的Item。當(dāng)然沒有Item出現(xiàn)了。
onLayoutChildren()
在LayoutManager中,所有Item的布局都是在onLayoutChildren()函數(shù)中處理的,所以我們?cè)贑ustomLayoutItem中添加onLayoutChildren()函數(shù):
@Overridepublic?void?onLayoutChildren(RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
????//定義豎直方向的偏移量
????int?offsetY?=?0;
????for?(int?i?=?0;?i?????????View?view?=?recycler.getViewForPosition(i);
????????addView(view);
????????measureChildWithMargins(view,?0,?0);
????????int?width?=?getDecoratedMeasuredWidth(view);
????????int?height?=?getDecoratedMeasuredHeight(view);
????????layoutDecorated(view,?0,?offsetY,?width,?offsetY?+?height);
????????offsetY?+=?height;
????}
}
在這個(gè)函數(shù)中,我主要做了兩個(gè)事:第一:把所有的item所對(duì)應(yīng)的view加進(jìn)來:
for?(int?i?=?0;?i?????View?view?=?recycler.getViewForPosition(i);????addView(view);
????…………
}
第二:把所有的Item擺放在它應(yīng)在的位置:
public?void?onLayoutChildren(RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{????//定義豎直方向的偏移量
????int?offsetY?=?0;
????for?(int?i?=?0;?i?????????…………
????????measureChildWithMargins(view,?0,?0);
????????int?width?=?getDecoratedMeasuredWidth(view);
????????int?height?=?getDecoratedMeasuredHeight(view);
????????layoutDecorated(view,?0,?offsetY,?width,?offsetY?+?height);
????????offsetY?+=?height;
????}
}
measureChildWithMargins(view, 0, 0);函數(shù)測(cè)量這個(gè)View,并且通過getDecoratedMeasuredWidth(view)得到測(cè)量出來的寬度,需要注意的是通過getDecoratedMeasuredWidth(view)得到的是item+decoration的總寬度。如果你只想得到view的測(cè)量寬度,通過View.getMeasuredWidth()就可以得到了。
然后通過layoutDecorated()函數(shù)將每個(gè)item擺放在對(duì)應(yīng)的位置,每個(gè)Item的左右位置都是相同的,從左側(cè)x=0開始擺放,只是y的點(diǎn)需要計(jì)算。所以這里有一個(gè)變量offsetY,用以累加當(dāng)前Item之前所有item的高度。從而計(jì)算出當(dāng)前item的位置。這個(gè)部分難度不大,就不再細(xì)講了。
在此之后,我們?cè)龠\(yùn)行程序,會(huì)發(fā)現(xiàn),現(xiàn)在item顯示出來了:
添加滾動(dòng)效果
但是,現(xiàn)在還不能滑動(dòng),如果我們要給它添加上滑動(dòng),需要修改兩個(gè)地方:
@Overridepublic?boolean?canScrollVertically()?{
????return?true;
}
@Override
public?int?scrollVerticallyBy(int?dy,?RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
????//?平移容器內(nèi)的item
????offsetChildrenVertical(-dy);
????return?dy;
}
我們通過在canScrollVertically()中return true;使LayoutManager具有垂直滾動(dòng)的功能。然后scrollVerticallyBy中接收每次滾動(dòng)的距離dy。如果你想使LayoutManager具有橫向滾動(dòng)的功能,可以通過在canScrollHorizontally()中`return true;``
這里需要注意的是,在scrollVerticallyBy中,dy表示手指在屏幕上每次滑動(dòng)的位移。
- 當(dāng)手指由下往上滑時(shí),dy>0
- 當(dāng)手指由上往下滑時(shí),dy<0
當(dāng)手指向上滑動(dòng)時(shí),我們需要讓所有子Item向上移動(dòng),向上移動(dòng)明顯是需要減去dy的。所以,大家經(jīng)過測(cè)試也可以發(fā)現(xiàn),讓容器內(nèi)的item移動(dòng)-dy距離,才符合生活習(xí)慣。在LayoutManager中,我們可以通過public void offsetChildrenVertical(int dy)函數(shù)來移動(dòng)RecycerView中的所有item。
現(xiàn)在我們?cè)龠\(yùn)行一下:
這里雖然實(shí)現(xiàn)了滾動(dòng),但是Item到頂之后,仍然可以滾動(dòng),這明顯是不對(duì)的,我們需要在滾動(dòng)時(shí)添加判斷,如果到頂了或者到底了就不讓它滾動(dòng)了。
判斷到頂
判斷到頂相對(duì)比較容易,我們只需要把所有的dy相加,如果小于0,就表示已經(jīng)到頂了。就不讓它再移動(dòng)就行,代碼如下:
private?int?mSumDy?=?0;@Override
public?int?scrollVerticallyBy(int?dy,?RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
????int?travel?=?dy;
????//如果滑動(dòng)到最頂部
????if?(mSumDy?+?dy?0)?{
????????travel?=?-mSumDy;
????}
????mSumDy?+=?travel;
????//?平移容器內(nèi)的item
????offsetChildrenVertical(-travel);
????return?dy;
}
在這段代碼中,通過變量mSumDy 保存所有移動(dòng)過的dy,如果當(dāng)前移動(dòng)的距離<0,那么就不再累加dy,直接讓它移動(dòng)到y(tǒng)=0的位置,因?yàn)橹耙呀?jīng)移動(dòng)的距離是mSumdy; 所以計(jì)算方法為:
travel+mSumdy?=?0;=>?travel?=?-mSumdy
所以要將它移到y(tǒng)=0的位置,需要移動(dòng)的距離為-mSumdy,效果如下圖所示:
從效果圖中可以看到,現(xiàn)在在到頂時(shí),就不會(huì)再移動(dòng)了。下面再來看看到底的問題。
判斷到底
判斷到底的方法,其實(shí)就是我們需要知道所有item的總高度,用總高度減去最后一屏的高度,就是到底的時(shí)的偏移值,如果大于這個(gè)偏移值就說明超過底部了。
所以,我們首先需要得到所有item的總高度,我們知道在onLayoutChildren中會(huì)測(cè)量所有的item并且對(duì)每一個(gè)item布局,所以我們只需要在onLayoutChildren中將所有item的高度相加就可以得到所有Item的總高度了。
private?int?mTotalHeight?=?0;public?void?onLayoutChildren(RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
????//定義豎直方向的偏移量
????int?offsetY?=?0;
????for?(int?i?=?0;?i?????????View?view?=?recycler.getViewForPosition(i);
????????addView(view);
????????measureChildWithMargins(view,?0,?0);
????????int?width?=?getDecoratedMeasuredWidth(view);
????????int?height?=?getDecoratedMeasuredHeight(view);
????????layoutDecorated(view,?0,?offsetY,?width,?offsetY?+?height);
????????offsetY?+=?height;
????}
????//如果所有子View的高度和沒有填滿RecyclerView的高度,
????//?則將高度設(shè)置為RecyclerView的高度
????mTotalHeight?=?Math.max(offsetY,?getVerticalSpace());
}
private?int?getVerticalSpace()?{
????return?getHeight()?-?getPaddingBottom()?-?getPaddingTop();
}
getVerticalSpace()函數(shù)可以得到RecyclerView用于顯示Item的真實(shí)高度。而相比上面的onLayoutChildren,這里只添加了一句代碼:mTotalHeight = Math.max(offsetY, getVerticalSpace());這里只所以取最offsetY和getVerticalSpace()的最大值是因?yàn)?#xff0c;offsetY是所有item的總高度,而當(dāng)item填不滿RecyclerView時(shí),offsetY應(yīng)該是比RecyclerView的真正高度小的,而此時(shí)的真正的高度應(yīng)該是RecyclerView本身所設(shè)置的高度。
接下來就是在scrollVerticallyBy中判斷到底并處理了:
public?int?scrollVerticallyBy(int?dy,?RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{????int?travel?=?dy;
????//如果滑動(dòng)到最頂部
????if?(mSumDy?+?dy?0)?{
????????travel?=?-mSumDy;
????}?else?if?(mSumDy?+?dy?>?mTotalHeight?-?getVerticalSpace())?{
????????travel?=?mTotalHeight?-?getVerticalSpace()?-?mSumDy;
????}
????mSumDy?+=?travel;
????//?平移容器內(nèi)的item
????offsetChildrenVertical(-travel);
????return?dy;
}
mSumDy + dy > mTotalHeight - getVerticalSpace()中:mSumDy + dy表示當(dāng)前的移動(dòng)距離,mTotalHeight - getVerticalSpace()表示當(dāng)滑動(dòng)到底時(shí)滾動(dòng)的總距離;
當(dāng)滑動(dòng)到底時(shí),此次的移動(dòng)距離要怎么算呢? 算法如下:
travel?+?mSumDy?=?mTotalHeight?-?getVerticalSpace();即此將將要移動(dòng)的距離加上之前的總移動(dòng)距離,應(yīng)該是到底的距離。=> travel = mTotalHeight - getVerticalSpace() - mSumDy;
現(xiàn)在再運(yùn)行一下代碼,可以看到,這時(shí)候的垂直滑動(dòng)列表就完成了:
從列表中可以看出,現(xiàn)在到頂和到底可以繼續(xù)滑動(dòng)的問題就都解決了。下面貼出完整的CustomLayoutManager代碼,供大家參考:
import?android.util.Log;
import?android.view.View;
import?androidx.recyclerview.widget.RecyclerView;
public?class?CustomLayoutManager?extends?RecyclerView.LayoutManager?{
????@Override
????public?RecyclerView.LayoutParams?generateDefaultLayoutParams()?{
????????return?new?RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
????????????????RecyclerView.LayoutParams.WRAP_CONTENT);
????}
????private?int?mTotalHeight?=?0;
????public?void?onLayoutChildren(RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
????????//定義豎直方向的偏移量
????????int?offsetY?=?0;
????????for?(int?i?=?0;?i?????????????View?view?=?recycler.getViewForPosition(i);
????????????addView(view);
????????????measureChildWithMargins(view,?0,?0);
????????????int?width?=?getDecoratedMeasuredWidth(view);
????????????int?height?=?getDecoratedMeasuredHeight(view);
????????????layoutDecorated(view,?0,?offsetY,?width,?offsetY?+?height);
????????????offsetY?+=?height;
????????}
????????//如果所有子View的高度和沒有填滿RecyclerView的高度,
????????//?則將高度設(shè)置為RecyclerView的高度
????????mTotalHeight?=?Math.max(offsetY,?getVerticalSpace());
????}
????private?int?getVerticalSpace()?{
????????return?getHeight()?-?getPaddingBottom()?-?getPaddingTop();
????}
????@Override
????public?boolean?canScrollVertically()?{
????????return?true;
????}
????private?int?mSumDy?=?0;
????@Override
????public?int?scrollVerticallyBy(int?dy,?RecyclerView.Recycler?recycler,?RecyclerView.State?state)?{
????????int?travel?=?dy;
????????//如果滑動(dòng)到最頂部
????????if?(mSumDy?+?dy?0)?{
????????????travel?=?-mSumDy;
????????}?else?if?(mSumDy?+?dy?>?mTotalHeight?-?getVerticalSpace())?{
????????????travel?=?mTotalHeight?-?getVerticalSpace()?-?mSumDy;
????????}
????????mSumDy?+=?travel;
????????//?平移容器內(nèi)的item
????????offsetChildrenVertical(-travel);
????????return?dy;
????}
}
完!
---END---
推薦閱讀:Android | 自定義上拉抽屜+組合動(dòng)畫效果重磅!!Gradle 6.6 發(fā)布,大幅提升性能!Flutter(Flare) 最有趣用戶交互動(dòng)畫沒有之一怒爬某破Hub站資源,只為擼這個(gè)鑒黃平臺(tái)!Flutter 10天高仿大廠App及小技巧積累總結(jié)阿里巴巴官方最新Redis開發(fā)規(guī)范!涉嫌侵害用戶權(quán)益,這101款A(yù)pp被點(diǎn)名!更文不易,點(diǎn)個(gè)“在看”支持一下?
總結(jié)
以上是生活随笔為你收集整理的viewpager 自定义翻页效果_Android RecyclerView自定义LayoutManager的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php分区表,【MYSQL】分区表
- 下一篇: 计算机考研的调查和改进建议