Vertical ViewPager and Android Pie Inconsistent Behavior with Swipe Gesture
After spending hell amount of time on SO, trying many GitHub
libraries and waiting for someone to respond on the bounty,
then I came up with below solutions and hope that it helps whoever needs it.
At first, this question got my attention and I think most of the answers over there are helpful, hence I upvoted them.
The main answer which helped me are link-1 and link-2.
Even though I have to make a few minor changes to ignore horizontal swipe
fling events.
Please do try this code and provide your feedback for any further improvements, thanks in advance. Happy Coding :)
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class VerticalViewPager extends ViewPager {
float x = 0;
float mStartDragX = 0;
private static final float SWIPE_X_MIN_THRESHOLD = 50; // Decide this magical nuber as per your requirement
public VerticalViewPager(Context context) {
super(context);
init();
}
public VerticalViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// The majority of the magic happens here
setPageTransformer(true, new VerticalPageTransformer());
// The easiest way to get rid of the overscroll drawing that happens on the left and right
setOverScrollMode(OVER_SCROLL_NEVER);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getAdapter() != null) {
if (getCurrentItem() >= 0 || getCurrentItem() < getAdapter().getCount()) {
swapXY(event);
final int action = event.getAction();
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
mStartDragX = event.getX();
if (x < mStartDragX
&& (mStartDragX - x > SWIPE_X_MIN_THRESHOLD)
&& getCurrentItem() > 0) {
Log.i("VerticalViewPager", "down " + x + " : " + mStartDragX + " : " + (mStartDragX - x));
setCurrentItem(getCurrentItem() - 1, true);
return true;
} else if (x > mStartDragX
&& (x - mStartDragX > SWIPE_X_MIN_THRESHOLD)
&& getCurrentItem() < getAdapter().getCount() - 1) {
Log.i("VerticalViewPager", "up " + x + " : " + mStartDragX + " : " + (x - mStartDragX));
mStartDragX = 0;
setCurrentItem(getCurrentItem() + 1, true);
return true;
}
break;
}
} else {
mStartDragX = 0;
}
swapXY(event);
return super.onTouchEvent(swapXY(event));
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = super.onInterceptTouchEvent(swapXY(event));
switch (event.getAction() & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
x = event.getX();
break;
}
swapXY(event); // return touch coordinates to original reference frame for any child views
return intercepted;
}
/**
* Swaps the X and Y coordinates of your touch event.
*/
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
private class VerticalPageTransformer implements PageTransformer {
@Override
public void transformPage(View view, float position) {
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 1) { // [-1,1]
view.setAlpha(1);
// Counteract the default slide transition
view.setTranslationX(view.getWidth() * -position);
//set Y position to swipe in from top
float yPosition = position * view.getHeight();
view.setTranslationY(yPosition);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}
}
Implement ViewPager inside Vertical ViewPager
You can use Vertical
RecyclerView
with Horizontal ViewPager
Here is the sample demo of your requirement with some bonus modification hope it helps you
MainActivity
public class MainActivity extends AppCompatActivity {
RecyclerView myRecyclerView;
private ArrayList<DataModel> arrayList = new ArrayList<>();
DataAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myRecyclerView = findViewById(R.id.myRecyclerView);
myRecyclerView.setHasFixedSize(true);
myRecyclerView.setLayoutManager(new LinearLayoutManager(this));
addData();
adapter = new DataAdapter(this, arrayList);
myRecyclerView.setAdapter(adapter);
}
private void addData() {
DataModel dataModel = new DataModel();
dataModel.setTitle("Image collection One");
ArrayList<Integer> list= new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(R.drawable.kid);
}
dataModel.setArrayList(list);
arrayList.add(dataModel);
DataModel dataModel2 = new DataModel();
dataModel2.setTitle("Image collection Two");
ArrayList<Integer> list2= new ArrayList<>();
for (int i = 0; i < 5; i++) {
list2.add(R.drawable.nilesh);
}
dataModel2.setArrayList(list2);
arrayList.add(dataModel2);
DataModel dataModel3 = new DataModel();
dataModel3.setTitle("Image collection Three");
ArrayList<Integer> list3= new ArrayList<>();
for (int i = 0; i < 5; i++) {
list3.add(R.drawable.kid);
}
dataModel3.setArrayList(list3);
arrayList.add(dataModel3);
DataModel dataModel4 = new DataModel();
dataModel4.setTitle("Image collection Four");
ArrayList<Integer> list4= new ArrayList<>();
for (int i = 0; i < 5; i++) {
list4.add(R.drawable.nilesh);
}
dataModel4.setArrayList(list4);
arrayList.add(dataModel4);
}
}
R.layout.activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/myRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
DataAdapter
public class DataAdapter extends RecyclerView.Adapter<DataAdapter.ViewHolder> {
private Context context;
private ArrayList<DataModel> arrayList;
public DataAdapter(Context context, ArrayList<DataModel> arrayList) {
this.context = context;
this.arrayList = arrayList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.custom_layout, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.tvTitle.setText(arrayList.get(position).getTitle());
ImageAdapter imageAdapter= new ImageAdapter(context,arrayList.get(position).getArrayList());
holder.myViewPager.setAdapter(imageAdapter);
holder.myTabLayout.setupWithViewPager(holder.myViewPager, true);
}
@Override
public int getItemCount() {
return arrayList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ViewPager myViewPager;
TextView tvTitle;
Button myButton;
TabLayout myTabLayout;
public ViewHolder(View itemView) {
super(itemView);
myViewPager=itemView.findViewById(R.id.myViewPager);
tvTitle=itemView.findViewById(R.id.tvTitle);
myButton=itemView.findViewById(R.id.myButton);
myTabLayout=itemView.findViewById(R.id.myTabLayout);
myButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "clicked position : "+getAdapterPosition(), Toast.LENGTH_SHORT).show();
}
});
}
}
}
R.layout.custom_layout
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="4dp"
app:cardUseCompatPadding="true"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#199bd2"
android:textSize="30sp"
android:textStyle="bold"
tools:text="Nilesh Rathod" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="250dp">
<android.support.v4.view.ViewPager
android:id="@+id/myViewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v4.view.ViewPager>
<android.support.design.widget.TabLayout
android:id="@+id/myTabLayout"
app:tabBackground="@drawable/tab_selector"
app:tabGravity="center"
android:layout_alignParentBottom="true"
app:tabIndicatorHeight="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
<Button
android:id="@+id/myButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click Me" />
</LinearLayout>
</android.support.v7.widget.CardView>
ImageAdapter
public class ImageAdapter extends PagerAdapter {
ArrayList<Integer> arrayList= new ArrayList<>();
private LayoutInflater inflater;
private Context context;
public ImageAdapter( Context context,ArrayList<Integer>imageArray) {
this.arrayList = imageArray;
this.context = context;
inflater= LayoutInflater.from(context);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getCount() {
return arrayList.size();
}
@Override
public Object instantiateItem(ViewGroup view, int position) {
View imageLayout = inflater.inflate(R.layout.pager_layout, view, false);
assert imageLayout != null;
final ImageView imageView = (ImageView) imageLayout
.findViewById(R.id.image);
imageView.setImageResource(arrayList.get(position));
view.addView(imageLayout, 0);
return imageLayout;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view.equals(object);
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
@Override
public Parcelable saveState() {
return null;
}
}
R.layout.pager_layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="1dip" >
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher"
android:scaleType="centerCrop" />
</LinearLayout>
DataModel
public class DataModel {
String title;
ArrayList<Integer> arrayList= new ArrayList<>();
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public ArrayList<Integer> getArrayList() {
return arrayList;
}
public void setArrayList(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
}
tab_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/selected_dot"
android:state_selected="true"/>
<item android:drawable="@drawable/default_dot"/>
</selector>
@drawable/selected_dot
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="8dp"
android:useLevel="false">
<solid android:color="@color/colorAccent"/>
</shape>
</item>
</layer-list>
@drawable/default_dot
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="8dp"
android:useLevel="false">
<solid android:color="@android:color/darker_gray"/>
</shape>
</item>
</layer-list>
OUTPUT
Related Topics
Get/Pick an Image from Android'S Built-In Gallery App Programmatically
Key Hash For Android-Facebook App
Best Practice For Storing and Protecting Private API Keys in Applications
What Is the Main Purpose of Settag() Gettag() Methods of View
Difference Between "@Id/" and "@+Id/" in Android
Android: Check Whether the Phone Is Dual Sim
Onactivityresult Method Is Deprecated, What Is the Alternative
"Arrayadapter Requires the Resource Id to Be a Textview" Xml Problems
Google Maps Android API V2 - Interactive Infowindow (Like in Original Android Google Maps)
Notification Not Showing in Oreo
Linear Layout and Weight in Android
Android Check Internet Connection
Communicating Between a Fragment and an Activity - Best Practices
Viewpager and Fragments - What's the Right Way to Store Fragment'S State
Android: Listview Elements With Multiple Clickable Buttons
How to Manually Include External Aar Package Using Gradle For Android