Android使用ViewPager2實現頁面滑動切換
作者:QiShare
轉載地址:https://juejin.cn/post/7065566099223347213
1.引言
在很多應用中,我們經常會看到多個頁面之間滑動切換的場景,ViewPager2是ViewPager的升級版,本文將簡要介紹如何使用ViewPager2、FragmentStateAdapter和Fragment來實現頁面之間的滑動切換。
2.實現頁面滑動切換
2.1 引入ViewPager2庫
要使用ViewPager2,需要引入ViewPager2庫,引入方法如下:
implementation "androidx.viewpager2:viewpager2:1.0.0"
2.2 使用ViewPager2
在佈局中使用ViewPager2,示例如下:
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
2.3 構建Fragment
本Fragment只爲簡單演示使用,其佈局如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@color/black"/>
</LinearLayout>
ContentFragment類的實現如下:
public class ContentFragment extends Fragment {
private String content;
public ContentFragment(String content) {
this.content = content;
}
private TextView tv_content;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, container, false);
tv_content = view.findViewById(R.id.tv_content);
tv_content.setText(content);
return view;
}
public void setContent(String content) {
this.content = content;
tv_content.setText(content);
}
}
2.4 繼承FragmentStateAdapter
創建自定義的類ContentPagerAdapter,讓它繼承FragmentStateAdapter,並實現createFragment(int position)和getItemCount()方法,示例如下:
public class ContentPagerAdapter extends FragmentStateAdapter {
private List<ContentFragment> datas;
public ContentPagerAdapter(@NonNull FragmentActivity fragmentActivity,List<ContentFragment> datas) {
super(fragmentActivity);
this.datas = datas;
}
@NonNull
@Override
public Fragment createFragment(int position) {
return datas.get(position);
}
@Override
public int getItemCount() {
return datas.size();
}
}
2.5 將ViewPager2與適配器綁定
將ViewPager2與適配器綁定後,便可實現頁面滑動切換,示例如下:
datas = new ArrayList<>();
datas.add(new ContentFragment("頁面1"));
datas.add(new ContentFragment("頁面2"));
datas.add(new ContentFragment("頁面3"));
datas.add(new ContentFragment("頁面4"));
datas.add(new ContentFragment("頁面5"));
contentPagerAdapter = new ContentPagerAdapter(this, datas);
viewPager2.setAdapter(contentPagerAdapter);
2.6 垂直方向滑動切換
ViewPager2不僅支持水平方向的滑動,還支持垂直方向的滑動,實現垂直滑動也是相當簡單,在佈局文件中添加android:orientation="vertical"屬性即可,如下所示:
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
或者在代碼中調用ViewPager2的setOrientation(ViewPager2.ORIENTATION_VERTICAL)方法也可以讓ViewPager2實現垂直方向的滑動。
2.7 Fragment更新
當Fragment集合發生變化需要更新時,使用FragmentStateAdapter進行更新也很便捷,由於ViewPager2是基於RecyclerView實現的,所以更新數據的時候可以調用notifyItemChanged(int position)、notifyItemInserted(int position)等方法進行更新。
3.總結
使用ViewPager2、FragmentStateAdapter和Fragment可以便捷地實現頁面之間的滑動切換,它不僅支持水平方向的滑動,還能通過簡單的設置就能實現垂直方向的滑動,靈活地運用ViewPager2能實現實際的需求。
android 垂直畫廊效果,Android利用PageView打造垂直的畫廊效果-Go語言中文社區
首先放一下效果圖
這裏是採用FragmentPageView所打造的效果
打造這樣效果第一步就是先讓ViewPager垂直滑動
這裏使ViewPager垂直滑動的代碼參考這位大神的帖子
https://www.jianshu.com/p/d3065bbc1167
其核心思想就是攔截觸摸事件並反轉橫向和垂直滑動。
打造好垂直滑動效果之後剩下就變得簡單,首先想達到這樣一個效果需要將ViewPager及其父元素添加屬性android:clipChildren=“false”,使得其可以超出自身回追範圍
這裏上一下這個效果的佈局
因爲PageViewr和外面的ViewGroup大小相同,因此一開始看到的就是一個Fragment
之後爲PageViewr設置
//預加載Fragment數量
verticalPageView.setOffscreenPageLimit(3);
//兩個Fragment之間的間距
verticalPageView.setPageMargin(60);
如果再加上放大縮小就可以達到畫廊效果了…但是我這裏不需要,有需要的可以自己添加
之後再爲自定義的ViewPager重寫onInterceptTouchEvent,在裏面加上這樣一段代碼:
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
this.animate().scaleX((float) 0.8).scaleY((float) 0.8).setDuration(300).start();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
this.animate().scaleX((float) 1.0).scaleY((float) 1.0).setDuration(300).start();
break;
}
注意這裏有一個坑,就是隻能觸發ACTION_DOWN事件,因此上面這些代碼應該在onInterceptTouchEvent一開始就調用
寫到現在應該就可以達到上面的效果了,但還有一個非常嚴重的問題,就是Fragment上面的點擊事件,這裏以上面的中止按鈕舉例
由於Android的觸摸事件是由頂層向下傳遞的,看上去沒什麼問題,但是這裏最頂層是ViewPager,因此無論如何都會先觸發ViewPager裏面的onInterceptTouchEvent,並執行縮放動畫。就算爲其中的按鈕執行onInterceptTouchEvent也只會讓ViewPager縮小並無法恢復,因爲只有ACTION_UP被阻攔
那麼就沒有辦法了麼?NO,這裏在ViewGroup之上的還有一個Activity,只要獲取按鈕在屏幕中的位置並根據觸摸點的位置判斷是否點擊到了按鈕即可。
這裏創建一個自定義接口
public interface IndexInterfaceForFragment {
void sendWidget(View view);
}
Activity實現這個接口
@Override
public void sendWidget(View view) {
interceptViewArray.add(view);
}
這裏我將控件都存入了一個數組中
之後Fragment在onCreateView中執行以下代碼
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_index_hint, container, false);
//注意:這個functionButton發送到了Activity
functionButton =view.findViewById(R.id.function_button);
Random random = new Random();
final int num = random.nextInt(100);
functionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(),"functionbutton點擊了!"+num,Toast.LENGTH_SHORT).show();
}
});
IndexInterfaceForFragment indexInterfaceForFragment = (IndexInterfaceForFragment)getActivity();
indexInterfaceForFragment.sendWidget(functionButton);
return view;
}
這裏可以看到,functionButton就是我們想阻攔的控件,通過
IndexInterfaceForFragment indexInterfaceForFragment = (IndexInterfaceForFragment)getActivity();
indexInterfaceForFragment.sendWidget(functionButton);
這種方式將控件發送到Activity中
在Activity中編寫以下代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN){
if (!interceptEventControl(interceptViewArray,ev)){
return false;
}
}
return super.dispatchTouchEvent(ev);
}
private boolean interceptEventControl(ArrayList arrayList, MotionEvent event){
for (View v : arrayList){
int[] location = {0,0};
v.getLocationInWindow(location);
Log.d("座標Button","X:"+location[0]+"Y:"+location[1]);
int left = location[0],top = location[1],bottom = top + v.getHeight(),right = left+v.getWidth();
if (event.getX() > left && event.getX() < right && event.getY() > top && event.getY() < bottom){
//點擊到了要阻攔的控件
v.callOnClick();
return false;
}
}
return true;
}
這裏重寫了dispatchTouchEvent並調用interceptEventControl做判斷,在interceptEventControl中編寫了判斷點擊區域的邏輯,如果發現點擊了控件就阻攔點擊事件的傳遞並調用控件的callOnClick()方法以觸發其點擊事件。
嗯,到這裏就完成了最開始Gif的效果