RecyclerView.notfiyDataSetChanged() 在横向布局中不起作用

RecyclerView.notfiyDataSetChanged() not working in landscape layout

我正在关注 Android Developer CodeLab (link)。将设备翻转为横向时,我无法获取 notifyDataSetChanged() 或 notifyItemRangeInserted() 来更新我的适配器。我在我的对象上实现了 Parcelable,它在从 onSaveInstanceState 包中拉出后正确地恢复了我的 ArrayList。但是当我调试这些 RecyclerView 方法时,适配器没有填充 ArrayList。此外,我注意到在横向模式下,将 ArrayList 重置为默认 11 项的 FAB 在设备翻转为横向模式时也不起作用。这让我相信可能出了什么问题 class.

MainActivity

public class MainActivity extends AppCompatActivity {

// Tag for key in instance save bundle
static final String SPORT_ARRAY = "Sport Array";

// Member variables
private RecyclerView mRecyclerView;
private ArrayList<Sport> mSportsData;
private SportsAdapter mAdapter;

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

    // Initialize the RecyclerView
    mRecyclerView = findViewById(R.id.recyclerView);

    // Set the Layout Manager
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

    // Initialize the ArrayList that will contain the data.
    mSportsData = new ArrayList<>();

    // Initialize the adapter and set it to the RecyclerView.
    mAdapter = new SportsAdapter(this, mSportsData);
    mRecyclerView.setAdapter(mAdapter);

    // Get the data or restore it
    if (savedInstanceState == null || !savedInstanceState.containsKey(SPORT_ARRAY)) {
        initializeData();
    } else {
        restoreData(savedInstanceState);
    }

    // Helper class for creating swipe to dismiss and drag and drop functionality.
    ItemTouchHelper helper = new ItemTouchHelper(
            new ItemTouchHelper.SimpleCallback(ItemTouchHelper.LEFT |
                    ItemTouchHelper.RIGHT | ItemTouchHelper.DOWN | ItemTouchHelper.UP,
                    ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
                /**
                 * Defines the drag and drop functionality.
                 *
                 * @param recyclerView The RecyclerView that contains the list items
                 * @param viewHolder The SportsViewHolder that is being moved.
                 * @param target The SportsViewHolder that you are switching the original one
                 *               with.
                 * @return true if the item was removed, false otherwise.
                 */
        @Override
        public boolean onMove(@NonNull RecyclerView recyclerView,
                              @NonNull RecyclerView.ViewHolder viewHolder,
                              @NonNull RecyclerView.ViewHolder target) {
            // Get the from and to positions.
            int from = viewHolder.getAdapterPosition();
            int to = target.getAdapterPosition();

            // Swap the items and notify the adapter.
            Collections.swap(mSportsData, from, to);
            mAdapter.notifyItemMoved(from, to);
            return true;
        }

                /**
                 * Defines the swipe to dismiss functionality.
                 * @param viewHolder The ViewHolder being swiped.
                 * @param direction The direction it is swiped in.
                 */
        @Override
        public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
            mSportsData.remove(viewHolder.getAdapterPosition());
            mAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
        }
    });

    // Attach the helper to the RecyclerView.
    helper.attachToRecyclerView(mRecyclerView);
}

/**
 * Method for initializing the sports data from resources.
 */
private void initializeData() {

    // Get the resources from the XML file
    String[] sportsList = getResources().getStringArray(R.array.sports_titles);
    String[] sportsInfo = getResources().getStringArray(R.array.sports_info);
    String[] sportsDetail = getResources().getStringArray(R.array.sports_detail_text);
    TypedArray sportsImageResources = getResources().obtainTypedArray(R.array.sports_images);

    // Clear the existing data (to avoid duplication)
    mSportsData.clear();

    // Create the ArrayList of Sports objects with the titles and information about each sport.
    for (int i = 0; i < sportsList.length; i++) {
        mSportsData.add(new Sport(sportsList[i], sportsInfo[i],
                sportsDetail[i], sportsImageResources.getResourceId(i, 0)));
    }

    // Recycle the sports image resource array.
    sportsImageResources.recycle();

    // Notify the adapter of the change.
    mAdapter.notifyDataSetChanged();
}

private void restoreData(Bundle savedBundle) {
    mSportsData.clear();
    mSportsData = savedBundle.getParcelableArrayList(SPORT_ARRAY);
    // Notify the adapter of inserted items
    mAdapter.notifyItemRangeInserted(0, mSportsData.size());
}

/**
 * onClick method for the FAB that resets the data.
 * @param view The button view that was clicked.
 */
public void resetSports(View view) {
    initializeData();
}

/**
 * Save the instance state
 * @param outState The current instance state
 */
@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putParcelableArrayList(SPORT_ARRAY, mSportsData);
    super.onSaveInstanceState(outState);
}
}

运动适配器

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

    // Member variables
    private ArrayList<Sport> mSportsData;
    private Context mContext;

    /**
     * Constructor that passes in the sports data and the context.
     * @param context Context of the application.
     * @param sportsData ArrayList containing the sports data.
     */
    SportsAdapter(Context context, ArrayList<Sport> sportsData) {
        this.mSportsData = sportsData;
        this.mContext = context;
    }

    /**
     * Required method for creating the ViewHolder objects.
     * @param viewGroup The ViewGroup into which the View will be added after it is bound to an
     *                  adapter position.
     * @param i The view type of the new View.
     * @return The newly created ViewHolder.
     */
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.list_item, viewGroup,
                false));
    }

    /**
     * Required method that binds the data to the ViewHolder.
     * @param viewHolder The ViewHolder into which the data should be put.
     * @param i The adapter position.
     */
    @Override
    public void onBindViewHolder(@NonNull SportsAdapter.ViewHolder viewHolder, int i) {
        // Get the current sport.
        Sport currentSport = mSportsData.get(i);
        // Populate the TextViews with data.
        viewHolder.bindTo(currentSport);
    }

    /**
     * Required method for determining the size of the data set.
     * @return Size of the data set.
     */
    @Override
    public int getItemCount() {
        return mSportsData.size();
    }

    /**
     * ViewHolder class that represents each row of data in the RecyclerVIew.
     */
    class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        // Member variables for the TextViews.
        private TextView mTitleText;
        private TextView mInfoText;
        private ImageView mSportsImage;

        /**
         * Constructor for the ViewHolder, used in onCreateViewHolder().
         * @param itemView The rootview of the list_item.xml layout file.
         */
        ViewHolder(View itemView) {
            super(itemView);

            // Initialize the views
            mTitleText = itemView.findViewById(R.id.title);
            mInfoText = itemView.findViewById(R.id.subTitle);
            mSportsImage = itemView.findViewById(R.id.sportsImage);

            // Set the OnClickListener to the entire view.
            itemView.setOnClickListener(this);
        }

        void bindTo(Sport currentSport) {
            // Populate the TextViews with data.
            mTitleText.setText(currentSport.getTitle());
            mInfoText.setText(currentSport.getInfo());
            Glide.with(mContext).load(currentSport.getImageResource()).into(mSportsImage);
        }

        /**
         * Handle click to show DetailActivity.
         *
         * @param v The view that was clicked.
         */
        @Override
        public void onClick(View v) {
            Sport currentSport = mSportsData.get(getAdapterPosition());
            Intent detailIntent = new Intent(mContext, DetailActivity.class);
            detailIntent.putExtra("title", currentSport.getTitle());
            detailIntent.putExtra("detail", currentSport.getDetail());
            detailIntent.putExtra("image_resource", currentSport.getImageResource());
            mContext.startActivity(detailIntent);
        }
    }
}

运动Class

public class Sport implements Parcelable {

    // Member variables representing the title and information about the sport.
    private String mTitle;
    private String mInfo;
    private String mDetail;
    private final int mImageResource;

    /**
     * Constructor for the Sport data model.
     * @param title The name of the sport.
     * @param info Information about the sport.
     * @param detail Details about the sport (from Wikipedia).
     * @param imageResource The image resource.
     */
    Sport(String title, String info, String detail, int imageResource) {
        this.mTitle = title;
        this.mInfo = info;
        this.mDetail = detail;
        this.mImageResource = imageResource;
    }

    protected Sport(Parcel in) {
        mTitle = in.readString();
        mInfo = in.readString();
        mDetail = in.readString();
        mImageResource = in.readInt();
    }

    public static final Creator<Sport> CREATOR = new Creator<Sport>() {
        @Override
        public Sport createFromParcel(Parcel in) {
            return new Sport(in);
        }

        @Override
        public Sport[] newArray(int size) {
            return new Sport[size];
        }
    };

    /**
     * Gets the title of the sport.
     * @return The title of the sport.
     */
    String getTitle() {
        return mTitle;
    }

    /**
     * Gets the info about the sport.
     * @return The info about the sport.
     */
    String getInfo() {
        return mInfo;
    }

    /**
     * Gets the details about the sport (from Wikipedia).
     * @return The details about the sport.
     */
    String getDetail() {
        return mDetail;
    }

    /**
     * Gets the image resource.
     * @return The image resource.
     */
    public int getImageResource() {
        return mImageResource;
    }

    /**
     * Describe the kinds of special objects contained in this Parcelable
     * instance's marshaled representation. For example, if the object will
     * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
     * the return value of this method must include the
     * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
     *
     * @return a bitmask indicating the set of special object types marshaled
     * by this Parcelable object instance.
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * Flatten this object in to a Parcel.
     *
     * @param dest  The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written.
     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mTitle);
        dest.writeString(mInfo);
        dest.writeString(mDetail);
        dest.writeInt(mImageResource);
    }
}

您没有正确更新适配器。当需要在 restoreData() 方法中从已保存的 Bundle 中恢复列表时,您可以执行以下操作:

mSportsData = savedBundle.getParcelableArrayList(SPORT_ARRAY);

这只是将 mSportsData 引用更改为指向 Bundle 中的列表。但是,RecyclerView 的适配器将基于您最初在 onCreate() 中创建的空列表:

mSportsData = new ArrayList<>();

这就是您的 FAB 也失败的原因,因为它在适配器未使用的列表上调用了 initializeData()。

更新适配器的正确方法是将列表中的对象放入 mSportsData 的 Bundle 中:

mSportsData.addAll(savedBundle.getParcelableArrayList(SPORT_ARRAY));