The author’s problem is this:
And: the old View has been returned to you in the form of convertView , if it is not null , then it is already configured. For example, a blocked button is returned to you, and then you draw numbers on it. That is, its initial position corresponded to the locked level, and everything was drawn correctly, but when you need to redraw the list, this (already configured button) returns, and you draw a number on top of it (since the current position corresponds to the locked level).
but from the comments it turned out that the author would be satisfied with the decision from scratch , so I publish it as an answer.
The result will be something like this:

First, we determine the data model.
What data is the adapter logic tied to? - on the data on the availability / inaccessibility of the level, therefore, as a data model, we are completely satisfied with a set of objects with one of two possible values of true / false - ArrayList<Boolean> .
Now we think how it all should look visually.
The visual component will be pretty primitive, just a button:
- If the level is available, then the button will say
Unlocked ; if unavailable, Locked ; - If the level is available, the button will be green; if it is unavailable, blue.
Functional? And what about without him
Add a little bit of some logic:
- If you click on the available level, then:
- if it has an even number , then add another available level to the table (at the end);
- if it has an odd number , then add another unavailable level to the table (at the end);
- When you click on an unavailable level, we will display a notification with the text “ Level locked! ".
I add these actions just for example to explain how to handle clicks on the corresponding table cells.
It's time to start acting
cell_item.xml - layout (or sketch) of a single table cell:
<?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" android:padding="4dp"> <Button android:id="@+id/cell_item_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="1dp" android:textSize="10sp"/> </LinearLayout>
Everything is simple - the usual button wrapped in LinearLayout .
main_activity.xml - layout main view (the table will be displayed in it):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <android.support.v7.widget.RecyclerView android:id="@+id/grid_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> </LinearLayout>
Here, too, everything is simple - RecyclerView , wrapped in LinearLayout .
Adapter that will manage our RecyclerView :
public class GridAdapter extends RecyclerView.Adapter<GridAdapter.CellViewHolder> { ArrayList<Boolean> mLevels; public GridAdapter(ArrayList<Boolean> levels) { mLevels = new ArrayList<>(levels); } @Override public CellViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_item, null); CellViewHolder cellViewHolder = new CellViewHolder(layoutView); return cellViewHolder; } @Override public void onBindViewHolder(CellViewHolder holder, int position) { if (mLevels.get(position)) { holder.mButton.setText("Unlocked"); holder.mButton.setBackgroundColor(Color.parseColor("#4AF764")); } else { holder.mButton.setText("Locked"); holder.mButton.setBackgroundColor(Color.parseColor("#7CC8F8")); } } @Override public int getItemCount() { return mLevels.size(); } public void addLevel(boolean isUnlocked) { mLevels.add(isUnlocked); } public class CellViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ public Button mButton; public CellViewHolder(View itemView) { super(itemView); mButton = (Button) itemView.findViewById(R.id.cell_item_button); mButton.setOnClickListener(this); } @Override public void onClick(View view) { if (!mLevels.get(getAdapterPosition())) { Toast.makeText(view.getContext(), "Level locked!", Toast.LENGTH_SHORT).show(); } else { if (getAdapterPosition() % 2 == 0) { addLevel(true); } else { addLevel(false); } notifyItemChanged(getItemCount() - 1); } } } }
When creating an adapter instance, we will pass it a list of ArrayList<Boolean> levels . The position number of the item in the list will be equal to the level number, true if the level is available and false otherwise.
Well, MainActivity , in which I add 10 levels available and 15 inaccessible:
public class MainActivity extends AppCompatActivity { RecyclerView mRecyclerView; GridAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView = (RecyclerView) findViewById(R.id.grid_recycler_view); mRecyclerView.setLayoutManager(new GridLayoutManager(this, 4)); ArrayList<Boolean> levels = new ArrayList<>(); for (int i=0; i<10; i++) { levels.add(true); } for (int i=0; i<15; i++) { levels.add(false); } mAdapter = new GridAdapter(levels); mRecyclerView.setAdapter(mAdapter); } }
In your case, you will need to fill the levels collection depending on the data in SharedPreferences .
UPD . About ViewHolder :
When you start the application, on the screen you see 40 table cells (that is, 40 View objects). At this stage, 40 + several more onCreateViewHolder(...) created, that is, the onCreateViewHolder(...) method is called for each cell, which creates a View , and the onBindViewHolder(...) method is called for each cell, which fills the cell with specific data.
After that, you scroll down the list, while the upper cells are no longer visible, but new cells appear from below. So, in order not to create new View for cells that appear on the bottom of the screen, use the old View , which left the screen (which were on top). That is, when displaying cells that appear from below, the onCreateViewHolder(...) method is no longer called, but only onBindViewHolder(...) is called, which receives one of the already rendered View , which is no longer visible, redraws this View and displays it from below.
The essence of the ViewHolder pattern is that it holds the link to the View , which will later be redrawn.
And you need this to improve performance and save memory, as if every time you created a new View , each time you would create a new object, the creation of which would run the findViewById(...) method, which is relatively resource-intensive.
ArrayList<Button> bto the adapter, but why do you never use it ingetView(...)? - post_zeewViewhas been returned to you in the form ofconvertView, if it is notnull, then it is already configured. For example, a blocked button is returned to you, and then you draw numbers on it. That is, its initial position corresponded to the locked level, and everything was drawn correctly, but when you need to redraw the list, this (already configured button) returns, and you draw a number on top of it (since the current position corresponds to the locked level). - post_zeew.size()is taken from it. Setting just a number ingetCount()does the same thing. - PolluxRecyclerView. Replaced byListViewandGridViewcameRecyclerView, which, depending on theLayoutManagercan display items as a list (both inListView) and a table (as inGridView). I do not use these obsolete components. So if you are satisfied with the solution withRecyclerView- write something approximate then I will outline. - post_zeew