GridView inside PageViewer has laggy scrolling
Asked Answered
P

4

5

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

Piranesi answered 11/2, 2018 at 7:41 Comment(7)
does any error can show in catlog?Highkey
no everything works fine, except touches get intercepted by a GridView, it is not really an errorPiranesi
which scroll laggs ? horizontal (pager) or vertical (gridview) ? I tested your code on devices 21, 22 and 23, everything seem ok (clickable items, no lag)Remit
@Remit by lag I mean sometimes it sticks, it is not as smooth as expected. for example if you try it many times you will observe that item inside gridview will intercept and prevent ViewPager from scrollingPiranesi
can you please given a github repository link of you code. I will compile in you above code in android studio it working fine. I need to take a pull of you code are identify the issue.Brocket
@Ashwanikumar please see updatePiranesi
@Piranesi I have see you code. I have a make RecyclerView with a layout manager of grid view. I think it is ok to you I have send a photo and push a code to a separated branch.Brocket
A
4

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

Alannaalano answered 14/2, 2018 at 18:48 Comment(1)
Thanks for the notes I will replace it with Recycler in the end. As I noted in my question I did try recycler instead of GridView, but it does not solve my problemPiranesi
D
2

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..

Dola answered 14/2, 2018 at 19:11 Comment(3)
I have tested this but unfortunately same result. also removed appabarScrollingBehaviour which is from original XML, this is simplified version which has same problemPiranesi
did you check both with true ??Dola
I just did, no help :(Piranesi
F
2

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
}
Foxtail answered 20/2, 2018 at 22:53 Comment(1)
this solution worked for me .. and smoothly! THanksDiminution
B
1
 **I make a change  with recyclerView with gridViewLayoutManager. please see the new change.**
    public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = findViewById(R.id.rvView);

        RecyclerGridView recyclerGridView = new RecyclerGridView(this);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 4, GridLayoutManager.HORIZONTAL, false));
        recyclerView.setAdapter(recyclerGridView);

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

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


public class RecyclerGridView extends RecyclerView.Adapter<RecyclerGridView.ViewHolder> {

    Context context;
    private ArrayList<AppModel> apps;

    public RecyclerGridView(Context context) {
        this.context = context;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View convertView = LayoutInflater.from(context).inflate(R.layout.app_item, null);
        return new ViewHolder(convertView);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.textView.setText(apps.get(position).getLabel());
    }

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

    public void setData(ArrayList<AppModel> items) {
        this.apps=items;

    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public ViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.text);
        }
    }
}




  <?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout 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"
    tools:context="com.test.apps.gridviewpager.MainActivity">

    <android.support.v4.view.ViewPager
        android:visibility="gone"
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="350dp"
        />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rvView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.constraint.ConstraintLayout>

this is 3 file to make a change the grid as per your required. it is ok and working fine, so I will pull the code to you branch.

Brocket answered 16/2, 2018 at 6:0 Comment(1)
Sorry sir but this is not what I need. While I understand how much better it can work, the behavior itself is different from what I am trying to achive. You see, I need pages and this is just scrolling, this is crucial that I have pages for my applicationPiranesi

© 2022 - 2024 — McMap. All rights reserved.