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