Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GridView inside PageViewer has laggy scrolling

I implemented a GridView inside PageViewer, but scrolling is not as smooth as it should be. Views inside GridView are intercepting touches.

This is driving me crazy I have tried everything and while one thing worked it is not good solution in the end. First here is my code.

apps_grid.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <GridView
        android:id="@+id/appGrid"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/ic_launcher_background"
        android:horizontalSpacing="4dp"
        android:numColumns="4"
        android:paddingTop="10dp"
        android:stretchMode="columnWidth"
        android:verticalSpacing="15dp"/>
</LinearLayout>

app_item.xml

<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="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="54dp"
        android:layout_height="54dp"
        android:layout_gravity="center_horizontal"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="5dp"
        android:ellipsize="end"
        android:gravity="center"
        android:paddingEnd="15dp"
        android:paddingStart="15dp"
        android:singleLine="true"
        android:text="title"
        android:textColor="@android:color/white"
        android:textSize="14sp" />

</LinearLayout>

activity_main.xml

<android.support.v4.view.ViewPager
    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:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="45dp"
    android:fillViewport="true"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    />

AppModel.java

public class AppModel {

    private String label;
    private String pkg;
    private Drawable mIcon;

    public AppModel(String name, String pkg, Drawable icon) {
        this.label = name;
        this.pkg = pkg;
        this.mIcon = icon;
    }

    public AppModel(String name) {
        this.label = name;
    }

    public String getLabel() {
        return this.label;
    }

    public String getPkg() {
        return this.pkg;
    }

    public Drawable getIcon() {
        return mIcon;
    }
}

AppListAdapter.java

public class AppListAdapter extends BaseAdapter {

    private ArrayList<AppModel> apps;
    private LayoutInflater layoutInflater;

    public AppListAdapter(Context context, ArrayList<AppModel> apps) {
        this.apps = apps;
        this.layoutInflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return apps.size();
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public Object getItem(int position) {
        AppModel app = apps.get(position);
        if(app == null) {
            return null;
        }

        return app;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        AppModel app = apps.get(position);

        if (convertView == null) {
            convertView = layoutInflater.inflate(R.layout.app_item, null);
        }

        if(app == null) {
            return convertView;
        }

        ((TextView)convertView.findViewById(R.id.text)).setText(app.getLabel());

        return convertView;
    }
}

TestPagerAdapter.java

public class TestPagerAdapter extends PagerAdapter {

    private Context mContext;
    private LayoutInflater inflater;
    private ArrayList<AppModel> apps;
    private int perPage = 12;

    public TestPagerAdapter(Context context) {
        this.mContext = context;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public Object instantiateItem(ViewGroup parent, int position) {
        ViewGroup layout = (ViewGroup) inflater.inflate(R.layout.apps_grid, parent, false);

        ArrayList<AppModel> pageApps = getAppsForPage(position);

        if(pageApps == null) {
            return layout;
        }

        final GridView appGrid = (GridView) layout.findViewById(R.id.appGrid);
        final AppListAdapter gridAdapter = new AppListAdapter(mContext, pageApps);
        appGrid.setVerticalScrollBarEnabled(false);
        appGrid.setAdapter(gridAdapter);
        parent.addView(layout);

        return layout;
    }

    @Override
    public void destroyItem(ViewGroup parent, int position, Object view) {
        parent.removeView((View) view);
    }

    @Override
    public int getCount() {
        if(apps != null) {
            return (int) Math.ceil((double) apps.size() / perPage);
        } else {
            return 0;
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    public void setData(ArrayList<AppModel> data) {
        if(data != null) {
            this.apps = data;
        }
    }

    public ArrayList<AppModel> getAppsForPage(int page) {
        if(apps == null) {
            return null;
        }

        int size = apps.size();
        int offset = getOffset(page);

        ArrayList<AppModel> pageApps = new ArrayList<AppModel>(size);

        for(int i = 0; i < perPage; i++) {
            if(offset+i < size) {
                pageApps.add(apps.get(offset+i));
            }
        }

        return pageApps;
    }

    public int getOffset(int page) {
        return perPage*page;
    }

}

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
    TestPagerAdapter mPAdapter = new TestPagerAdapter(this);
    viewPager.setAdapter(mPAdapter);

    ArrayList<AppModel> items = new ArrayList<AppModel>(20);

    for(int i=0;i<20;i++) {
        AppModel app = new AppModel(""+i);
        items.add(app);
    }
    mPAdapter.setData(items);
    mPAdapter.notifyDataSetChanged();
}

So, here is what I tried: I replaced GridView with RecyclerView. Added all this attributes on EVERY View

android:clickable="false"
android:contextClickable="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:focusedByDefault="false"
android:isScrollContainer="false"
android:longClickable="false"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
android:touchscreenBlocksFocus="false"

Overriding OnTouchListener (and many other) on GridView and returning true. probably tried some other things that I cannot remember right now.

The only thing that worked was settings android:filterTouchesWhenObscured="true" attribute on GridView while some other applications view was on top (so condition "WhenObscured" was satisfied) When I tried this my ViewPager started scrolling very smoothly, in other cases it sometimes does not scroll (probably when GridView intercepts it).

Note that items inside GridView in the end must be clickable.

UPDATE

https://github.com/mpa4hu/GridViewPager this is a github link to the project, to simulate the problem scroll forward and backward with gesture similar to this picture

gesture

like image 281
CBeTJlu4ok Avatar asked Feb 11 '18 07:02

CBeTJlu4ok


3 Answers

Recycler view recycles the view and doesn't recreate the view until required. It just re-binds the view.

RecyclerView was created as a ListView improvement, so yes, you can create an attached list with ListView control, but using RecyclerView is easier as it:

Reuses cells while scrolling up/down - this is possible with implementing View Holder in the listView adapter, but it was an optional thing, while in the RecycleView it's the default way of writing adapter.

Decouples list from its container - so you can put list items easily at run time in the different containers (linearLayout, gridLayout) with setting LayoutManager.

Example:

mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//or
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//number 
of columns in a grid layout

There is more about RecyclerView, but I think these points are the main ones.

So, to conclude, RecyclerView is a more flexible control for handling "list data" that follows patterns of delegation of concerns and leaves for itself only one task - recycling items.

You can refer about its advantages here : RecyclerView over ListView

like image 186
niketshah09 Avatar answered Sep 28 '22 09:09

niketshah09


I have this silver bullet class to solve my own nestedScrolling issues:

class NonScrollableRecyclerView : RecyclerView {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean = false
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean = false
}

So you can create a custom-non-scrollable GridView and use it instead a regular GridView:

class NonScrollableGridView : GridView {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean = false
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean = false
}
like image 39
Fredy Mederos Avatar answered Sep 28 '22 07:09

Fredy Mederos


Due to appabarScrollingBehaviour the scroll of gridView lags ... So you have disable the nested scrolling behaviour of your gridView , to do so...just add..

ViewCompat.setNestedScrollingEnabled(listView/gridview,false);

in your case use your gridView ...

(add Android Support v4 Library 23.1 or +) here

These will hopefully allow your GridView you to scroll smoothly...

Happy Coding :)

Edit

You can also try android:fastScrollEnabled="true" to your gridView layout.. in xml.. this might help..

like image 33
Santanu Sur Avatar answered Sep 28 '22 08:09

Santanu Sur