Low performance in Recyclerview with Constraintlayout, inside a Viewpager on Android

Asked

Viewed 243 times

1

I have a Recyclerview with about 20 items. It takes a long time to render and blocks the UI.

I’m using the Constraintlayout to position items on each line. They’re not as complex as you can see in the preview.

I also have a Viewpager for main navigation (see bottom bar). In each Fragment there is also a Viewpager. This Viewpager is where Recyclerview is located (inside a Nestedscrollview).

Additionally I have a Nestedscrollview scroll reader to make a Parallax effect on the header.

I would like to point out that if you remove a few items and leave Recyclerview with only 3 items, it gets much faster. So it seems to be a problem with Recyclerview lines.

I also ran a test and replaced the Constraintlayout of each line with a Framelayout with fixed height - Performance did not improve

I’m also using the Iconics (for font icon) and Calligraphy (for custom typography) - This may be affecting performance?


Preview:

Watch out for the touch circle when I try to scroll immediately after switching to the first page - the UI is locked. It is also visible that when switching to the second page, the slide animation occurs, but when trying to switch to the first page, the animation blocks, immediately appearing the list

UI a bloquear quando se entra na primeira página

Item of each row (history_item.xml):

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

    <android.support.constraint.ConstraintLayout
        android:id="@+id/container"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="76dp"
        android:clickable="true">

        <TextView
            android:id="@+id/date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintHorizontal_chainStyle="spread"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            android:textSize="11sp"
            android:textAllCaps="true"
            android:textColor="@color/black"
            android:text="12 JUN"
            android:layout_marginTop="@dimen/margin_xs"
            android:layout_marginLeft="@dimen/margin_small"/>

        <com.mikepenz.iconics.view.IconicsTextView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/date"
            app:layout_constraintLeft_toLeftOf="@id/date"
            app:layout_constraintRight_toLeftOf="@+id/kms"
            android:layout_marginRight="5dp"
            android:layout_marginTop="5dp"
            android:text="{evz_workout_running}"
            android:textColor="@color/colorPrimary"
            android:textSize="22sp"
            android:gravity="left" />




        <TextView
            android:id="@+id/kms"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toRightOf="@id/icon"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/icon_time"
            app:layout_constraintHorizontal_chainStyle="spread"
            android:layout_marginTop="@dimen/margin_xs"
            android:textSize="34sp"
            android:textColor="@color/black"
            android:textAppearance="@style/textStyleBlack"
            android:text="325,4"
            android:translationY="-12dp" />

        <TextView
            android:id="@+id/kms_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="@id/kms"
            app:layout_constraintRight_toRightOf="@id/kms"
            android:layout_marginTop="32dp"
            android:textSize="9sp"
            android:textColor="@color/black"
            android:text="KMS"
            android:textAppearance="@style/textStyleBold"/>




        <com.mikepenz.iconics.view.IconicsTextView
            android:id="@+id/icon_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toRightOf="@id/kms"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/icon_speed"
            android:layout_marginTop="@dimen/margin_xs"
            android:layout_marginLeft="@dimen/margin_medium"
            android:layout_marginRight="@dimen/margin_medium"
            app:layout_constraintHorizontal_chainStyle="spread"
            android:text="{evz_config_duration}"
            android:textColor="@color/gray"
            android:textSize="26sp" />

        <TextView
            android:id="@+id/time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/icon_time"
            app:layout_constraintLeft_toLeftOf="@id/icon_time"
            app:layout_constraintRight_toRightOf="@id/icon_time"
            android:layout_marginTop="5dp"
            android:textSize="10sp"
            android:textColor="@color/black"
            android:textAppearance="@style/textStyleBold"
            android:text="00:00:07"/>



        <com.mikepenz.iconics.view.IconicsTextView
            android:id="@+id/icon_speed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="@id/icon_time"
            app:layout_constraintLeft_toRightOf="@id/icon_time"
            app:layout_constraintRight_toLeftOf="@+id/icon_points"
            app:layout_constraintHorizontal_chainStyle="spread"
            android:paddingTop="2sp"
            android:paddingBottom="2sp"
            android:text="{evz_rate}"
            android:textColor="@color/gray"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/speed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/icon_speed"
            app:layout_constraintLeft_toLeftOf="@id/icon_speed"
            app:layout_constraintRight_toRightOf="@id/icon_speed"
            android:layout_marginTop="5dp"
            android:textSize="10sp"
            android:textColor="@color/black"
            android:textAppearance="@style/textStyleBold"
            android:text="00'00''"/>


        <com.mikepenz.iconics.view.IconicsTextView
            android:id="@+id/icon_points"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="@id/icon_time"
            app:layout_constraintLeft_toRightOf="@id/icon_speed"
            app:layout_constraintRight_toRightOf="parent"
            android:layout_marginRight="@dimen/margin_small"
            android:paddingTop="2dp"
            android:paddingBottom="2dp"
            android:text="{evz_star}"
            android:textColor="@color/gray"
            android:textSize="22sp"/>

        <TextView
            android:id="@+id/points"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/icon_points"
            app:layout_constraintLeft_toLeftOf="@id/icon_points"
            app:layout_constraintRight_toRightOf="@id/icon_points"
            android:layout_marginTop="5dp"
            android:textSize="10sp"
            android:textColor="@color/black"
            android:textAppearance="@style/textStyleBold"
            android:text="3500"/>

        <Button
            android:id="@+id/undo_button"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_margin="0dp"
            android:layout_gravity="end|center_vertical"
            android:background="@color/red"
            android:textColor="@color/white"
            style="@style/Base.Widget.AppCompat.Button.Borderless"
            android:textAppearance="@style/textStyleBold"
            android:textAllCaps="true"
            android:text="Anular"
            android:textSize="18sp"
            android:visibility="gone"
            />

</android.support.constraint.ConstraintLayout>

Fragment that has Recyclerview (pager_fragment.xml):

<?xml version="1.0" encoding="utf-8"?>
    <merge
        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">

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/sub_scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:paddingTop="210dp">

                <pt.sportzone.everyzone.ui.DynamicViewPager
                    android:id="@+id/sub_pager"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"/>

            </LinearLayout>


        </android.support.v4.widget.NestedScrollView>

        <android.support.constraint.ConstraintLayout
            android:id="@+id/sub_header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            ...


        </android.support.constraint.ConstraintLayout>

</merge>

The Fragment class that has Viewpager (Pagerfragment.java):

public class PagerFragment extends Fragment implements IPagerFragment, ViewPager.OnPageChangeListener, View.OnClickListener {

    ...

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Get elements from Layout
        mPager = (DynamicViewPager) view.findViewById(R.id.sub_pager);

        if (mButton1 != null && mButton2 != null && mButton3 != null) {

...

            if (mHeader != null) {
                // Listen to when tree is ready so we can setup parallax
                mHeader.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        mHeader.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        setupParallax();
                    }
                });
            }
        }


        if (mPager != null) mPager.setCurrentItem(0);
        activate();
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (!isVisibleToUser) activated = false;
        active = isVisibleToUser;
    }

    protected void activate() {
        if (active && getView() != null && getActivity() != null && !activated) {
            activated = true;
        }
    }


    @Override
    public void onPageSelected(int position) {
        if (mPager == null) return;

        //Log.d(EZ.TAG, "Page Selected: " + position);
        FragmentPagerAdapter adapter = (FragmentPagerAdapter) mPager.getAdapter();
        targetPosition = position;
        IPagerFragment fragmentAppear = (IPagerFragment) adapter.getItem(targetPosition);
        IPagerFragment fragmentDisappear = (IPagerFragment) adapter.getItem(currentPosition);
        fragmentDisappear.willDisappear(targetPosition);
        fragmentAppear.willAppear(currentPosition);

        mButton1.setSelected(false);
        mButton2.setSelected(false);
        mButton3.setSelected(false);

        switch (position) {
            case 0:
                mButton1.setSelected(true);
                break;
            case 1:
                mButton2.setSelected(true);
                break;
            case 2:
                mButton3.setSelected(true);
                break;
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mPager == null) return;

        //Log.d(EZ.TAG, position + " - " + positionOffset + " - " + positionOffsetPixels);
        FragmentPagerAdapter adapter = (FragmentPagerAdapter) mPager.getAdapter();
        //currentPosition = position;
        if (position == targetPosition && positionOffsetPixels == 0) {
            IPagerFragment fragmentAppear = (IPagerFragment) adapter.getItem(targetPosition);
            IPagerFragment fragmentDisappear = (IPagerFragment) adapter.getItem(currentPosition);
            currentPosition = targetPosition;
            fragmentDisappear.didDisappear();
            fragmentAppear.didAppear();
        }
    }


    @Override
    public void onPageScrollStateChanged(int state) {
    }

    public void setupParallax() {

        // Listen for scroll events
        if (scrollChangedListener == null) {

            // Get initial data
            initialWidth = mBackground.getWidth();
            initialHeight = mBackground.getHeight();
            initialRatio = initialHeight / initialWidth;
            curveInitialHeight = mCurve.getHeight();

            if (mScroll != null && collapseHeader) {
                mScroll.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
                    @Override
                    public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                        doParallax(v, scrollX, scrollY, oldScrollX, oldScrollY);
                    }
                });
            }

            UI.flattenConstraintRatio(mBackground);
            UI.flattenConstraintRatio(mBackgroundSpacer);
            UI.flattenConstraintRatio(mCurve);

            mCurve.setScaleType(ImageView.ScaleType.FIT_XY);

            // Run first parallax iteration
            doParallax(mScroll, 0, 0, 0, 0);

        }
    }

    public void doParallax(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

        // No height, something's wrong, just go away
        if (initialHeight == 0) return;

        // Get the percentage of scroll till hitting the max scroll target (initial Height)
        float percent = (float)scrollY / curveInitialHeight;

        float percentLeft = 1 - percent;

        if (percent < 0) percent = 0;
        if (percent > 1) percent = 1;

        float percentFast = Math.min(percent * 1.4f, 1);
        float percentLeftFast = 1 - percentFast;

        // Adjust the profile background image
        int headerHeight = Math.max((int)initialHeight - (int)(percent * curveInitialHeight), (int)initialHeight - (int)curveInitialHeight); // 24 -> status bar, 53 -> action bar
        UI.setConstraintHeight(mBackground, headerHeight);
        int curveHeight = Math.max((int)(percentLeft * curveInitialHeight), 0);
        UI.setConstraintHeight(mCurve, curveHeight);


        if (mButton1 != null) {
            mButton1.getButtonLabel().setAlpha(percentLeft);
            mButton2.getButtonLabel().setAlpha(percentLeft);
            mButton3.getButtonLabel().setAlpha(percentLeft);
        }

        // Adjust Elements alpha
        //mBackground.getDrawable().setAlpha((int)(percentLeftFast * 255));
        //mGradient.setAlpha(percentLeft);

    }

And the Adapter (Historyadapter.java):

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

private static final int PENDING_REMOVAL_TIMEOUT = 3000; // 3sec

private Fragment fragment;

List<Music> items;
List<Music> itemsPendingRemoval;
boolean undoOn;

private Handler handler = new Handler(); // hanlder for running delayed runnables
HashMap<Music, Runnable> pendingRunnables = new HashMap<>(); // map of items to pending runnables, so we can cancel a removal if need be

public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    Music track;

    public TextView mDate;
    public IconicsTextView mIcon;
    public TextView mKms;
    public IconicsTextView mIconTime;
    public TextView mTime;
    public IconicsTextView mIconSpeed;
    public TextView mSpeed;
    public IconicsTextView mIconPoints;
    public TextView mPoints;
    public ConstraintLayout mContainer;
    public Button mUndo;

    OnHistoryItemClickedListener mListener;

    public ViewHolder(Fragment fragment, View holder) {
        super(holder);

        // Force interface implementation
        if (fragment instanceof OnHistoryItemClickedListener) {
            mListener = (OnHistoryItemClickedListener) fragment;
        } else {
            throw new RuntimeException(fragment.toString()
                    + " must implement OnHistoryItemClickedListener");
        }
        mContainer = (ConstraintLayout) holder.findViewById(R.id.container);
        mDate = (TextView) holder.findViewById(R.id.date);
        mIcon = (IconicsTextView) holder.findViewById(R.id.icon);
        mKms = (TextView) holder.findViewById(R.id.kms);
        mIconTime = (IconicsTextView) holder.findViewById(R.id.icon_time);
        mTime = (TextView) holder.findViewById(R.id.time);
        mIconSpeed = (IconicsTextView) holder.findViewById(R.id.icon_speed);
        mSpeed = (TextView) holder.findViewById(R.id.speed);
        mIconPoints = (IconicsTextView) holder.findViewById(R.id.icon_points);
        mPoints = (TextView) holder.findViewById(R.id.points);

        mUndo = (Button) holder.findViewById(R.id.undo_button);

        mContainer.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v == mContainer) {
            mListener.onHistoryItemClicked(this.getLayoutPosition(), track);
        }
    }

}

public interface OnHistoryItemClickedListener {
    void onHistoryItemClicked(int position, Music music);
    void onHistoryItemRemoved(int position, Music music);
}

public HistoryAdapter(Fragment fragment, List<Music> items) {
    this.fragment = fragment;
    this.items = items;
    this.itemsPendingRemoval = new ArrayList<>();
}

@Override
public HistoryAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = (LayoutInflater) fragment.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View track = inflater.inflate(R.layout.history_item, parent, false);
    return new HistoryAdapter.ViewHolder(fragment, track);
}

@Override
public void onBindViewHolder(HistoryAdapter.ViewHolder holder, int position) {
    final Music item = items.get(position);

    if (itemsPendingRemoval.contains(item)) {
        // we need to show the "undo" state of the row
        holder.itemView.setBackgroundColor(Color.RED);
        //holder.titleTextView.setVisibility(View.GONE);
        holder.mUndo.setVisibility(View.VISIBLE);
        holder.mUndo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // user wants to undo the removal, let's cancel the pending task
                Runnable pendingRemovalRunnable = pendingRunnables.get(item);
                pendingRunnables.remove(item);
                if (pendingRemovalRunnable != null) handler.removeCallbacks(pendingRemovalRunnable);
                itemsPendingRemoval.remove(item);
                // this will rebind the row in "normal" state
                notifyItemChanged(items.indexOf(item));
            }
        });
    } else {
        // we need to show the "normal" state
        holder.itemView.setBackgroundColor(Color.WHITE);
        //holder.titleTextView.setVisibility(View.VISIBLE);
        //holder.titleTextView.setText(item);
        holder.mUndo.setVisibility(View.GONE);
        holder.mUndo.setOnClickListener(null);
    }

    holder.track = item;

    if (position == getItemCount() - 1) {
        holder.mContainer.setBackground(ContextCompat.getDrawable(fragment.getContext(), R.drawable.row_with_arrow_no_border));
    } else {
        holder.mContainer.setBackground(ContextCompat.getDrawable(fragment.getContext(), R.drawable.row_with_arrow));
    }
}

...
  • 4

    Welcome to the Stackoverflow in Portuguese. As the name suggests, the official language used here is Portuguese. So, could you please translate your question? If you prefer, you can also ask the same question on Stackoverflow website in English.

  • This is the [en.so], please translate your question.

  • Thank you, it’s already translated.

No answers

Browser other questions tagged

You are not signed in. Login or sign up in order to post.