markdown RecyclerView最佳实践(传统,非MVP方法)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown RecyclerView最佳实践(传统,非MVP方法)相关的知识,希望对你有一定的参考价值。

# RecyclerView Best Practices (Traditional, non MVP approach)

## 1. Static ViewHolder

[SOURCE](http://androidshenanigans.blogspot.com/2015/02/viewholder-pattern-common-mistakes.html)

For very simple adapter, we often make the `ViewHolder` as inner class. However, Inner classes or non-static nested classes will keep a reference to the outer class. This means that for each `ViewHolder` object you create, it will contain a reference to the whole Adapter. This could lead to some serious memory problems so you should always declare your `ViewHolder` as static. Or better yet, just create a new file for `ViewHolder` as the app will surely expand and the Adapter can be very bloated if we make `ViewHolder` as inner class. 

## 2. Don't add `OnClickListener()` inside `onBindViewHolder()` 

[SOURCE](https://stackoverflow.com/a/33845951/1602807), [SOURCE](https://stackoverflow.com/a/33524897/1602807)

`onBindViewHolder()` will get called repeatedly and thus, repeating adding `OnClickListener()` is unnecessary. There are two places we can add `OnClickListener()`: in `onCreateViewHolder()` or inside the `ViewHolder` constructor. We will use the latter as it is more explicit. For complex, operation, maybe the former will be more appropriate.

## 3. Use the dataset as the single source of truth

[SOURCE](https://stackoverflow.com/a/45624248/1602807), [SOURCE](https://stackoverflow.com/a/47142825/1602807)

Always use the state from dataset to update the `ViewHolder` and also, remember to cover all `if-else` condition in `onBindViewHolder()` to prevent weird state.


```java
if(likes.contains(mUserId)){
            myHolder.button_like.setEnabled(false);
            myHolder.button_like.setBackground(mContext.getResources().getDrawable(R.drawable.filled_star));
            myHolder.number_likes.setText("foo "+mpackid.optJSONObject("info").optString("likes")+" foo");
            myHolder.number_likes.setTextColor(Color.parseColor("#525252"));
        }
 else{
     //behaviour when like.contains(userId) is false.
 }
```

A few comments on the adapter:

- Make sure **not to hard-code any visibilities** inside `onBindViewHolder()`. Use your data set instead.

- Attach the `onClickListeners` **inside the constructor of the `ViewHolder`** class.

- Inside the listener(s), be careful to do both: **refresh the view and update the model**. This is the crucial point.

## 4. Do NOT pass dataset to the ViewHolder

[SOURCE](https://stackoverflow.com/a/38577915/1602807) (modified a little bit to prevent repeatedly attaching listener)

If we use the approach to create `onClickListeners` inside `ViewHolder` constructor, we will need to interact with the dataset somehow to update the state of the model. We naive approach is to pass an reference of the dataset into the ViewHolder. DON'T. The ViewHolder's only responsibility is displaying the data. Updating the data is the responsibility of the Adapter.  

## Sample  Code

In your Fragment/ Activity...

```java
mRecyclerView = v.findViewById(R.id.fragment_reward_recycler_view);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
        mRecyclerView.setLayoutManager(layoutManager);
        mAdapter = new RewardAdapter(generateTestContent()); // test data, random list
        mRecyclerView.setAdapter(mAdapter);
```

***item_reward.xml***

```xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:custom="http://schemas.android.com/tools">

    <ImageView
        android:id="@+id/item_reward_icon"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:src="@drawable/discount"/>

    <co.shutta.shuttapro.widgets.FontText
        android:id="@+id/item_reward_text_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:maxLines="2"
        android:textColor="@color/black_tint_design"
        android:textSize="14sp"
        android:textStyle="bold"
        custom:typefaceAsset="fonts/SF-Semibold.ttf"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/item_reward_text_expiry_date"
        app:layout_constraintStart_toEndOf="@+id/item_reward_icon"
        app:layout_constraintEnd_toStartOf="@+id/item_reward_redeem_button"/>

    <co.shutta.shuttapro.widgets.FontText
        android:id="@+id/item_reward_text_expiry_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="2dp"
        android:maxLines="1"
        android:textColor="@color/dark_gray"
        android:textSize="12sp"
        custom:typefaceAsset="fonts/SF-Regular.ttf"
        app:layout_constraintTop_toBottomOf="@+id/item_reward_text_title"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/item_reward_icon"/>

    <co.shutta.shuttalibrary.views.shinebutton_0_2_0.ShineButton
        android:id="@+id/item_reward_redeem_button"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:src="@android:color/darker_gray"
        app:btn_color="@android:color/darker_gray"
        app:btn_fill_color="@color/md_light_green_500"
        app:allow_random_color="true"
        app:enable_flashing="true"
        app:click_animation_duration="200"
        app:shine_animation_duration="1500"
        app:shine_turn_angle="20"
        app:small_shine_offset_angle="20"
        app:small_shine_color="#CC9999"
        app:shine_count="8"
        app:siShape="@raw/smile"
        android:layout_marginEnd="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <View
        android:id="@+id/item_reward_divider"
        android:layout_width="0dp"
        android:layout_height="1dp"
        android:background="@color/gray"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
```

***Reward***

```java
public class Reward {
    private String mTitle;
    private String mExpiryDate;
    private boolean mIsSelected;

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }

    public String getExpiryDate() {
        return mExpiryDate;
    }

    public void setExpiryDate(String expiryDate) {
        mExpiryDate = expiryDate;
    }

    public boolean isSelected() {
        return mIsSelected;
    }

    public void setSelected(boolean selected) {
        mIsSelected = selected;
    }
}

```

***RewardAdapter***

```java
public class RewardAdapter extends RecyclerView.Adapter<RewardAdapter.ViewHolder> {
    private List<Reward> mRewardList;

    private interface OnRedeemButtonClickListener{
        void onRedeemButtonClick(ShineButton v, int position);
    }

    // THIS SHOULD BE A SEPERATED FILE
    protected static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        private ImageView rewardIcon;
        private FontText rewardTitle;
        private FontText rewardExpiryDate;
        private ShineButton redeemButton;
        private OnRedeemButtonClickListener listener;

        public ViewHolder(View v, OnRedeemButtonClickListener listener) {
            super(v);
            rewardIcon = v.findViewById(R.id.item_reward_icon);
            rewardTitle = v.findViewById(R.id.item_reward_text_title);
            rewardExpiryDate = v.findViewById(R.id.item_reward_text_expiry_date);
            redeemButton = v.findViewById(R.id.item_reward_redeem_button);
            redeemButton.setOnClickListener(this);
            this.listener = listener;
        }

        @Override
        public void onClick(View v) {
            if(listener != null)
                listener.onRedeemButtonClick((ShineButton) v, getAdapterPosition());
        }

        public ImageView getRewardIcon() {
            return rewardIcon;
        }

        public FontText getRewardTitle() {
            return rewardTitle;
        }

        public FontText getRewardExpiryDate() {
            return rewardExpiryDate;
        }

        public ShineButton getRedeemButton() {
            return redeemButton;
        }
    }

    public void add(int position, Reward item) {
        mRewardList.add(position, item);
        notifyItemInserted(position);
    }

    public void remove(int position) {
        mRewardList.remove(position);
        notifyItemRemoved(position);
    }

    public RewardAdapter(List<Reward> myDataset) {
        mRewardList = myDataset;

        //to prevent messed up animation when use notifyDatassetChanged(), see http://stackoverflow.com/a/29792793/1602807 for details, doesn't need step 1
        setHasStableIds(true);
    }

    // Create new views (invoked by the layout manager)
    @Override
    public RewardAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // create a new view
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View v = inflater.inflate(R.layout.item_reward, parent, false);
        // set the view's size, margins, paddings and layout parameters
        return new ViewHolder(v, mRedeemButtonClickListener);
    }

    private OnRedeemButtonClickListener mRedeemButtonClickListener = new OnRedeemButtonClickListener() {
        @Override
        public void onRedeemButtonClick(ShineButton v, int position) {
            mRewardList.get(position).setSelected(v.isChecked());
        }
    };

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        final Reward reward = mRewardList.get(position);
        holder.getRewardTitle().setText(reward.getTitle());
        holder.getRewardExpiryDate().setText(reward.getExpiryDate());
        holder.getRedeemButton().setChecked(reward.isSelected());
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return mRewardList.size();
    }

    @Override //to prevent messed up animation when use notifyDataSetChanged()
    public long getItemId(int position) {
        return mRewardList.get(position).hashCode();
    }
}

```

以上是关于markdown RecyclerView最佳实践(传统,非MVP方法)的主要内容,如果未能解决你的问题,请参考以下文章

markdown 用Python开发的“最佳实践最佳”(BOBP)指南。

markdown 用Python开发的“最佳实践最佳”(BOBP)指南。

markdown 用Python开发的“最佳实践最佳”(BOBP)指南。

markdown 用Python开发的“最佳实践最佳”(BOBP)指南。

markdown 用Python开发的“最佳实践最佳”(BOBP)指南。

markdown 用Python开发的“最佳实践最佳”(BOBP)指南。