Get the position of a button clicked in ListView - java

I have a fragment where when an item of the list is clicked, you can determine which position using onListItemClick. Now I have a button on each list item (a delete button) and I want to know which position was clicked when it is pressed. How can I do this? it seems like the onClick method for the button has to be in a different Activity so I don't know how I can know which position of it was clicked. Here is the fragment code:
public class MapListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
private static final int LOADER_ID = 1;
private static final String[] FROM = { Database.Maps.DATA,
Database.Maps.NAME };
private static final String[] CURSOR_COLUMNS = { Database.Maps.ID,
Database.Maps.DATA, Database.Maps.NAME };
private static final int[] TO = { R.id.li_map_image, R.id.li_map_name };
private SimpleCursorAdapter mAdapter;
// FIXME isn't this unnecessary?
public MapListFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// FIXME reverse the order so the newest sessions are at the top
mAdapter = new SimpleCursorAdapter(getActivity(),
R.layout.map_list_item, null, FROM, TO, 0);
mAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor,
int columnIndex) {
if (view.getId() == R.id.li_map_image) {
((ImageView) view).setImageURI(Uri.parse(cursor
.getString(columnIndex)));
return true;
}
return false;
}
});
setListAdapter(mAdapter);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public void onListItemClick(ListView list, View v, int position, long id) {
final Intent nextIntent = new Intent(getActivity(),
ViewMapActivity.class);
nextIntent.putExtra(Utils.Constants.MAP_ID_EXTRA, id);
startActivity(nextIntent);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), DataProvider.MAPS_URI,
CURSOR_COLUMNS, null, null, null);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (loader.getId() == LOADER_ID)
mAdapter.swapCursor(cursor);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
and the 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="50dp"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal" >
<ImageView
android:id="#+id/li_map_image"
android:layout_width="50dp"
android:layout_height="match_parent"
android:contentDescription="thumbnail" />
<TextView
android:id="#+id/li_map_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:paddingLeft="8dp"
android:textSize="16sp" />
<ImageButton
android:layout_width="60dp"
android:layout_height="match_parent"
android:id="#+id/delete"
android:focusableInTouchMode="true"
android:background="#drawable/red_x"
android:layout_gravity="center|left"
android:onClick="deleteMap"/>
</LinearLayout>

You should be able to get the parent of the ImageButton inside the ImageButton click handler. Assuming you add each button listener inside the list item creation for the adapter
ImageButton btn_image = (ImageButton) itemview.findViewById(R.id.delete);
btn_image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
View parentItem = view.getParent();
}
});
Where itemview is the inflated view in the getView method of your list adapter.
You have a rather complicated need for an simple adapter. Take a look at this tutorial http://www.vogella.com/tutorials/AndroidListView/article.html#adapterown where he walks you through making your own custom adapter. You'll see the "getView" method inside the custom list view adapter. This is where you setup each list item and would add the button.

First you can put a hidden field in the list item so that when you click. From the view you can find this field (id) and get its value.

Related

Android synchronize a button on different pages

I am fairly new to Android and java. I am trying to make an application with multiple pages that you can swipe through. I started from a ViewPager2 example that is using a RecyclerView. It has 2 layout files. A main one and a viewPager one that is used for all the different pages, but with a different background color and title.
I have added a switch button on the viewpager xml and want to synchronize this button so it has the same state on all pages. But it does not do that out of the box. It seems the switch is created again for each of the different pages and I don't know how to access them on the other pages when the button on the current page is being changed.
It seems like a very simple thing to do, but I cannot find how to do it. Below is the code for my 2 java files.
Any help would be greatly appreciated.
public class MainActivity extends AppCompatActivity {
ViewPager2 viewPager2;
boolean continuous;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager2 = findViewById(R.id.viewPager2);
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageSelected (int position) {
if (continuous == true) continuous = false;
else continuous = true;
int pos = position;
}
});
List<String> list = new ArrayList<>();
list.add("First Screen");
list.add("Second Screen");
list.add("Third Screen");
list.add("Fourth Screen");
viewPager2.setAdapter(new ViewPagerAdapter(this, list, viewPager2));
}
}
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewHolder> {
private List<String> mData;
private LayoutInflater mInflater;
private ViewPager2 viewPager2;
private int[] colorArray = new int[]{android.R.color.black, android.R.color.holo_blue_dark, android.R.color.holo_green_dark, android.R.color.holo_red_dark};
ViewPagerAdapter(Context context, List<String> data, ViewPager2 viewPager2) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
this.viewPager2 = viewPager2;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_viewpager, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
String animal = mData.get(position);
holder.myTextView.setText(animal);
holder.relativeLayout.setBackgroundResource(colorArray[position]);
}
#Override
public int getItemCount() {
return mData.size();
}
// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder {
TextView myTextView;
RelativeLayout relativeLayout;
Button button;
Switch switch2;
ViewHolder(View itemView) {
super(itemView);
myTextView = itemView.findViewById(R.id.tvTitle);
relativeLayout = itemView.findViewById(R.id.container);
button = itemView.findViewById(R.id.btnToggle);
switch2 = itemView.findViewById(R.id.switch2);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(viewPager2.getOrientation() == ViewPager2.ORIENTATION_VERTICAL)
viewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
else{
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
}
}
});
switch2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (switch2.isChecked()) {
button.setEnabled(false);
} else {
button.setEnabled(true);
}
}
});
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/login"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="top|left"
app:tint="#color/white"
android:layout_margin="#dimen/small"
android:layout_marginTop="#dimen/small"
android:src="#drawable/ic_account_circle_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"/>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/screen_viewpager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="#+id/tab_indicator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
This will help if you want to place the button on each page you should add it to your activity file and then place it on the position of the view pager
It seems like you are placing a different button on each page in the ViewPager but what you probably want is to place the button in the Activity layout?

How to set OnClickListener to Expandable RecycleView

I want to set OnClickListeners to the items from my Expandable Recycleview. Each item from the Recycleview should have a button ( like this https://imgur.com/qlEJCkk : a + button to add tasks and an "x" button for each task to delete it)
I have tried to implement it from some other examples of onClickListeners but nothing worked so far
this is the ADAPTER:
public class ExpandableAdapter extends ExpandableRecyclerViewAdapter<RoutineViewHolder, TaskViewHolder> {
public ExpandableAdapter(List<? extends ExpandableGroup> groups) {
super(groups);
}
#Override
public RoutineViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_routine, parent, false);
return new RoutineViewHolder(v);
}
#Override
public TaskViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_task, parent, false);
return new TaskViewHolder(v);
}
#Override
public void onBindChildViewHolder(TaskViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) {
final Tasks tasks = (Tasks) group.getItems().get(childIndex);
holder.bind(tasks);
}
#Override
public void onBindGroupViewHolder(RoutineViewHolder holder, int flatPosition, ExpandableGroup group) {
final Routine routine = (Routine) group;
holder.bind(routine);
}
RoutineViewHolder:
import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder;
public class RoutineViewHolder extends GroupViewHolder implements View.OnClickListener {
private TextView mTextView;
public RoutineViewHolder(View itemView) {
super(itemView);
mTextView = itemView.findViewById(R.id.exp_routine);
itemView.setOnClickListener(this);
}
public void bind(Routine routine){
mTextView.setText(routine.getTitle());
}
}
TaskViewHolder:
public class TaskViewHolder extends ChildViewHolder {
private TextView mTextView;
private CheckBox mCheckBox;
private Boolean checkVal;
public TaskViewHolder(View itemView) {
super(itemView);
mTextView = itemView.findViewById(R.id.exp_task);
mCheckBox=itemView.findViewById(R.id.exp_task_checkbox);
}
public void bind(Tasks tasks) {
mTextView.setText(tasks.name);
checkVal=((tasks.checkBox==1)?Boolean.TRUE:Boolean.FALSE);
mCheckBox.setChecked(checkVal);
}
}
as you can see I have 2 ViewHolders : RoutineViewHolder and TaskViewHolder. I am very confused to where and how I should set the OnClickListener since I want it to behave different for the "Routines" and "Tasks" because they would have different buttons.
"Tasks" should have the + button to add tasks underneath it
and each task should have an X button to delete that specific task
the expandable recycleview is made out of 2 more of these "Tasks" cathegories.
I would prefer a solution where I can listen all these callbacks from some higher level (like Activity) where I can change data objects and refresh the RecyclerView keep things in sync based on callbacks. (This is eventually what you will need if you scale this.)
I implemented your code and modified a little to get the expected result.
For this solution:
I created an interface to get callbacks
On Add clicked on Routine
On Delete clicked on Task
On check status changed on Task
Made my activity to implement that interface and passed it to adapter.
Adapter passes it to ViewHolder
ViewHolder will invoke required function upon click.
In call backs:
For ADD: You can know which Routine was clicked
For Delete: You can know ParentRoutine, Child Index of Task and Task
For Check Change: You can know ParentRoutine, Child Index of Task, Task and New check status.
Code
1. Add new file ListActionListener.java
This is the interface.
public interface ListActionListener {
// Know add was clicked on given routine
void onAddTaskClicked(Routine routine);
// Know delete was clicked on given task.
void onDeleteTaskClicked(Routine routine, Tasks task, int index);
// Know checkbox clicked on given task (with new checked status)
void onTaskCheckChanged(Routine routine, Tasks task, int index, boolean checked);
}
2. Make your activity implement this interface. ExpandableListActivity.java
This is my sample activity that you see in the screenshots.
public class ExpandableListActivity extends AppCompatActivity implements ListActionListener{
ExpandableAdapter adapter;
RecyclerView recyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_expandable_list);
recyclerView = findViewById(R.id.recyclerView);
loadList();
}
private void loadList() {
List<Routine> routines = getDummyRoutineList();
adapter = new ExpandableAdapter(routines, this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
private List<Routine> getDummyRoutineList() {
List<Routine> list = new ArrayList<Routine>();
Tasks rt1 = new Tasks("R1 Tasks1", 1);
Tasks rt2 = new Tasks("R1 Tasks2", 0);
Tasks rt3 = new Tasks("R1 Tasks3", 1);
Tasks rt4 = new Tasks("R1 Tasks4", 0);
Tasks rt5 = new Tasks("R1 Tasks5", 0);
List<Tasks> r1Tasks = new ArrayList<>();
r1Tasks.add(rt1);
r1Tasks.add(rt2);
r1Tasks.add(rt3);
r1Tasks.add(rt4);
r1Tasks.add(rt5);
Routine r1 = new Routine("Routine 1", r1Tasks);
Tasks r2t1 = new Tasks("R2 Tasks1", 1);
Tasks r2t2 = new Tasks("R2 Tasks2", 0);
Tasks r2t3 = new Tasks("R2 Tasks3", 1);
Tasks r2t4 = new Tasks("R2 Tasks4", 0);
Tasks r2t5 = new Tasks("R2 Tasks5", 1);
List<Tasks> r2Tasks = new ArrayList<>();
r2Tasks.add(r2t1);
r2Tasks.add(r2t2);
r2Tasks.add(r2t3);
r2Tasks.add(r2t4);
r2Tasks.add(r2t5);
Routine r2 = new Routine("Routine 2", r2Tasks);
list.add(r1);
list.add(r2);
return list;
}
#Override
public void onAddTaskClicked(Routine routine) {
Toast.makeText(this, "On Add Clicked", Toast.LENGTH_SHORT).show();
}
#Override
public void onDeleteTaskClicked(Routine routine, Tasks task, int index) {
Toast.makeText(this, "On Delete Clicked", Toast.LENGTH_SHORT).show();
}
#Override
public void onTaskCheckChanged(Routine routine, Tasks task, int index, boolean checked) {
Toast.makeText(this, "On Check changed:"+checked, Toast.LENGTH_SHORT).show();
}
}
3. Add "X" button to Task Row layout
This is my sample XML file, your XML may look different. Main thing is to add button for Delete.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp"
>
<Button
android:id="#+id/btn_delete"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="X"
/>
<CheckBox
android:id="#+id/exp_task_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
/>
<TextView
android:id="#+id/exp_task"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="#+id/btn_delete"
android:layout_toRightOf="#+id/exp_task_checkbox"
/>
</RelativeLayout>
4. Add "+" button to Routine Layout file
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp"
>
<Button
android:id="#+id/btn_add"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="+"
/>
<TextView
android:id="#+id/exp_routine"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="#+id/btn_delete"
android:layout_toRightOf="#+id/exp_task_checkbox"
/>
</RelativeLayout>
5. Update Adapter to accept a ListActionListener
public class ExpandableAdapter extends ExpandableRecyclerViewAdapter<RoutineViewHolder, TaskViewHolder> {
ListActionListener listActionListener;
public ExpandableAdapter(List<? extends ExpandableGroup> groups, ListActionListener listActionListener) {
super(groups);
this.listActionListener = listActionListener;
}
#Override
public RoutineViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_routine, parent, false);
return new RoutineViewHolder(v);
}
#Override
public TaskViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.expandable_recyclerview_task, parent, false);
return new TaskViewHolder(v);
}
#Override
public void onBindChildViewHolder(TaskViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) {
final Tasks tasks = (Tasks) group.getItems().get(childIndex);
holder.bind((Routine)group, childIndex, tasks, listActionListener);
}
#Override
public void onBindGroupViewHolder(RoutineViewHolder holder, int flatPosition, ExpandableGroup group) {
final Routine routine = (Routine) group;
holder.bind(routine, listActionListener);
}
}
6. Update TaskViewHolder.java
To accepte listener and invoke callback
public class TaskViewHolder extends ChildViewHolder {
private TextView mTextView;
private CheckBox mCheckBox;
private Boolean checkVal;
private Button btnDelete;
public TaskViewHolder(View itemView) {
super(itemView);
mTextView = itemView.findViewById(R.id.exp_task);
mCheckBox=itemView.findViewById(R.id.exp_task_checkbox);
btnDelete = itemView.findViewById(R.id.btn_delete);
}
public void bind(final Routine parentRoutine, final int childIndex, final Tasks tasks, final ListActionListener listActionListener) {
mTextView.setText(tasks.name);
checkVal=((tasks.checkBox==1)?Boolean.TRUE:Boolean.FALSE);
mCheckBox.setChecked(checkVal);
//add delete button click
btnDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
listActionListener.onDeleteTaskClicked(parentRoutine, tasks, childIndex);
}
});
mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
//to avoid initial call back
if(checked != checkVal) {
listActionListener.onTaskCheckChanged(parentRoutine, tasks, childIndex, checked);
checkVal = checked;
}
}
});
}
}
7. Update RoutineViewHolder.java
To accepte listener and invoke callback.
public class RoutineViewHolder extends GroupViewHolder implements View.OnClickListener {
private TextView mTextView;
private Button btnAdd;
public RoutineViewHolder(View itemView) {
super(itemView);
mTextView = itemView.findViewById(R.id.exp_routine);
btnAdd = itemView.findViewById(R.id.btn_add);
itemView.setOnClickListener(this);
}
public void bind(final Routine routine, final ListActionListener listActionListener) {
mTextView.setText(routine.getTitle());
btnAdd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
listActionListener.onAddTaskClicked(routine);
}
});
}
}
Bingo.... Run the code... :)
Have you tried to call setChildClickListener of your ExpandableAdapter object from your activity?
Have a look at this:
ExpandableAdapter adapter=new ExpandableAdapter(myExpandableGroupList);
adapter.setChildClickListener(new OnCheckChildClickListener() {
#Override
public void onCheckChildCLick(View v, boolean checked, CheckedExpandableGroup group,int childIndex) {
}
});
I hope this helps you.

Checkbox for a row in ListView

I want to add, for each row in ListView, a checkbox which will be activated and shown on long press, I don't know whether I think correctly, I should add in row layout a Checkbox which is default hidden and when action start all check box on list will be shown and able to check?
To show CheckBox on each row:
1. Add an extra boolean variable isLongPressed to your adapter class and initialized with default false value from adapter constructor.
2. In your adapter getView()/ onBindViewHolder() method add an condition like this:
CheckBox checkBox = (CheckBox) view.findViewById(R.id.check);
if(isLongPressed)
{
checkBox.setVisibility(View.VISIBLE);
} else {
checkBox.setVisibility(View.GONE);
}
3. Add an method showCheckbox() to your adapter class to update ListView with checkbox visible state.
public void showCheckbox()
{
isLongPressed = true;
notifyDataSetChanged(); // Required for update
}
4. Call showCheckbox() from onItemLongClick:
list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, "Long Click", Toast.LENGTH_SHORT).show();
your_adapter.showCheckbox();
return true;
}
});
Here is good tutorial about Contextual Action Mode
Hope this will help~
Try this:
We will use a recyclerview, and a checkbox adapter
Chechbox Adapter layout
<?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="#dimen/checkbox_adapter_item_height"
android:gravity="center_vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal">
<CheckBox
android:id="#+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:theme="#style/MyCheckBoxTheme"
android:clickable="false"
android:longClickable="false"
android:focusable="false"/>
<TextView
android:id="#+id/tvItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item1"
android:visibility="visible"
android:padding="6dp"
android:paddingStart="12dp"
android:textColor="#color/colorMaterialBlack"
android:textSize="16sp" />
</LinearLayout>
A style for checkbox, keep this in style
<style name="MyCheckBoxTheme" parent="Theme.AppCompat.Light">
<item name="colorControlNormal">#color/colorBlackDimText</item>
<item name="colorControlActivated">#color/greenStatus</item>
</style>
A Model for the Checkbox adapter you can define/add/remove vars in your model
public class CheckboxTitlesData {
private String title;
private boolean isSelected;
public CheckboxTitlesData(String title, boolean isSelected) {
this.title = title;
this.isSelected = isSelected;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
Checkbox Adapter
public class CheckboxTitleAdapter extends RecyclerView.Adapter<CheckboxTitleAdapter.ViewHolder> implements GenericAdapterInterface{
List<CheckboxTitlesData> dataList = new ArrayList<>();
private CheckboxTitlesData previousSelection;
protected MyApplication.MenuSelectionListener listener;
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(getRootLayout(), parent, false);
return new ViewHolder(view);
}
public CheckboxTitleAdapter(MyApplication.MenuSelectionListener listener){
this.listener = listener;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
CheckboxTitlesData data = dataList.get(position);
if (data.isSelected()){
previousSelection = data;
holder.checkBox.setChecked(true);
}else holder.checkBox.setChecked(false);
holder.tvItem.setText(data.getTitle());
}
#Override
public int getItemCount() {
return dataList.size();
}
#Override
public void changeData(List dataList) throws IllegalArgumentException{
if (dataList == null || dataList.size() <= 0)
return;
if (!(dataList.get(0) instanceof CheckboxTitlesData))
throw new IllegalArgumentException("Required data type \"CheckboxTitlesData\"");
this.dataList.clear();
this.dataList.addAll(dataList);
(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
}, 100);
}
#Override
public int getRootLayout() {
return R.layout.adapter_title_checkbox;
}
#Override
public void setOnClickListener(RecyclerView.ViewHolder holder) {
holder.itemView.setOnLongClickListener((View.OnLongClickListener) holder);
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
#Bind(R.id.tvItem)
TextView tvItem;
#Bind(R.id.checkbox)
CheckBox checkBox;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
itemView.setOnClickListener(this);
setOnClickListener(this);
}
#Override
public boolean onLongClick(View v) {
final int pos = dataList.indexOf(previousSelection);
if (pos == getAdapterPosition())
return;
if (listener != null)
listener.onMenuSelected(dataList.get(getAdapterPosition()));
CheckboxTitlesData data = dataList.get(getAdapterPosition());
data.setSelected(true);
dataList.set(getAdapterPosition(), data);
if (pos != -1) {
previousSelection.setSelected(false);
dataList.set(pos, previousSelection);
}
(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
}, 100);
return true
}
}
}
Interface, u can remove the interface if you wanted, I just use this for my adapters usually for readability for other dev:
public interface GenericAdapterInterface {
void changeData(List dataList) throws Exception;
int getRootLayout();
void setOnClickListener(RecyclerView.ViewHolder holder);
}
Recycler view layout xml, add the recyclerview whr you need, this is just an eg
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/llBody"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingEnd="#dimen/padding_normal"
android:paddingStart="#dimen/padding_normal">
<android.support.v7.widget.RecyclerView
android:id="#+id/rvMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
Activity/fragment that uses the recycler view must do this
#Bind(R.id.rvMenu)
RecyclerView rvMenu;
private CheckboxTitleAdapter menuAdapter;
//Define an interface for callback on long press
public interface YourOwnInterface {
void onLonPress(Object data);
}
private void setUpRecycleView() {
RecyclerView.LayoutManager mLayoutManager = new
LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
rvMenu.setHasFixedSize(false);
rvMenu.setLayoutManager(mLayoutManager);
rvMenu.setItemAnimator(new DefaultItemAnimator());
YourOwnInterface listener = new YourOwnInterface () {
#Override
public void onLonPress(Object data) {
updateView((CheckboxTitlesData) data);
}
};
//this interface is needed wen a longpress is made adapter and the callback is given to your Acitivity/Fragment you can perform necessary opreation
menuAdapter = new CheckboxTitleAdapter(listener);
rvMenu.setAdapter(menuAdapter);
}
private void updateView(CheckboxTitlesData data) {
//perform operation on long press
}
Done it works

What is the best practice for updating a RecyclerView within View.OnClick?

Not certain the best way to tackle this. I have a RecyclerView that represents a list of records from a database (I'm using SugarOrm; but, that's inconsequential to the question).
I'd like to represent data changes and allow the user to do CRUD functionality via onClick and onLongClick events. For example, if a user long-presses on a view in the RecyclerView, I'd like them to have the option to delete the record. The problem is that an update is easy to reflect in the view using only the ViewHolder; but, deleting a record is not so easy. The ViewHolder, as a static inner class, doesn't have access to the RecyclerView itself to to modify the adapter data or notify that the data changed.
One option is that I could make the inner ViewHolder class not static; but, should I be concerned about potential memory leaks? It feels like that would be the simplest solution; but, is there completely different pattern that I should be using (such as having another class be the onClickListener)?
I'd like to keep my code readable and as standard practice as I can; but, not if it will violate best practices or become inefficient.
See below for clarity.
SugarOrm Model Class to Display In ViewHolder:
public class SomeModel extends SugarRecord{
#Column(name="Name")
public String name;
#Column(name="AddedDate")
public Date addedDate = new Date();
}
RecyclerViewAdapter and ViewHolder:
public class SomeModelRecyclerViewAdapter
extends RecyclerView.Adapter<SomeModelRecyclerViewAdapter.ViewHolder>{
private List<SomeModel> data;
public SomeModelRecyclerViewAdapter(List<SomeModel> data) {
this.data = data;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.model_item, parent, false);
ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(holder);
view.setOnLongClickListener(holder);
return holder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.someModel = data.get(position);
holder.bindData();
}
#Override
public int getItemCount() {
return data.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnLongClickListener {
private static final SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
SomeModel someModel;
TextView modelNameLabel;
TextView modelDateLabel;
public SomeModel getSomeModel() {
return someModel;
}
public void setSomeModel(SomeModel someModel) {
this.someModel = someModel;
}
public ViewHolder(View itemView) {
super(itemView);
}
public void bindData() {
modelNameLabel = (TextView) itemView.findViewById(R.id.modelNameLabel);
modelDateLabel = (TextView) itemView.findViewById(R.id.modelDateLabel);
modelNameLabel.setText(someModel.name);
modelDateLabel.setText(dateFormat.format(someModel.addedDate));
}
#Override
public void onClick(View v) {
someModel.addedDate = new Date();
someModel.save();
bindData();
}
#Override
public boolean onLongClick(View v) {
someModel.delete();
return true;
}
}
}
Activity:
public class MainActivity extends AppCompatActivity {
EditText modelName;
Button addModelButton;
RecyclerView modelList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
modelName = (EditText) findViewById(R.id.modelName);
addModelButton = (Button) findViewById(R.id.addModelButton);
modelList = (RecyclerView) findViewById(R.id.modelList);
addModelButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SomeModel newRecord = new SomeModel();
newRecord.name = modelName.getText().toString();
newRecord.save();
setupRecyclerView();
}
});
setupRecyclerView();
}
private void setupRecyclerView() {
List<SomeModel> allModels = SomeModel.listAll(SomeModel.class);
SomeModelRecyclerViewAdapter adapter = new SomeModelRecyclerViewAdapter(allModels);
modelList.setHasFixedSize(true);
modelList.setLayoutManager(new LinearLayoutManager(this));
modelList.setAdapter(adapter);
}
}
Activity layout (activity_main.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.aaronmbond.recyclerviewdilemaexample.MainActivity">
<EditText
android:id="#+id/modelName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
/>
<Button
android:id="#+id/addModelButton"
android:layout_alignParentStart="true"
android:layout_below="#id/modelName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/addModelButtonText"
/>
<android.support.v7.widget.RecyclerView
android:id="#+id/modelList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/addModelButton"
android:layout_alignParentStart="true"
/>
</RelativeLayout>
RecyclerView Item Layout (model_item.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/modelNameLabel"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="#+id/modelDateLabel"
android:layout_alignParentStart="true"
android:layout_below="#id/modelNameLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</RelativeLayout>
I ran into a similar requirement few days back. You are right about the part - The ViewHolder, as a static inner class, doesn't have access to the RecyclerView itself to to modify the adapter data or notify that the data changed.
So the way to handle this is to define an interface which encapsulates all operations within your viewholder which it need to trigger something on the RecyclerView. Sample interface definition from my code -
/**
* Parent fragment or activity to implement this interface to listen to item deletes.
* Item deletes effect the state of the parent
*/
public interface OnItemModifiedListener {
void itemDeleted(Cart.CartItem item);
void itemQuantityChanged(Cart.CartItem item, int newQuantity);
void itemRemovedAll();
}
The parent fragment or Activity implements this interface and passes it on to the Adapter as part of it's constructor. Sample constructor from my code again -
public SimpleItemRecyclerViewAdapter(Context context, List<Cart.CartItem> items, OnItemModifiedListener l) {
//this variable is declared as a adapter state variable
mItemModifiedListener = l;
}
Now when a certain operation happens within your viewholder (specifically clicks) then invoke the appropriate method on this interface. Sample again from my code where i invoke this interface when a row is deleted -
holder.mDeleteView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//show an alert to user to confirm before remving the item from cart
AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).create();
//alertDialog.setTitle("Alert");
alertDialog.setMessage(getString(R.string.alert_remove_item_from_cart_text));
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mValues.remove(holder.mItem);
if(null != mItemModifiedListener)mItemModifiedListener.itemDeleted(holder.mItem);
notifyItemRemoved(position);
//notifyDataSetChanged();
}
});
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, getString(android.R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
alertDialog.show();
}
});
the below link is a good read too - https://antonioleiva.com/recyclerview-listener/
Hope this helps...

Swipe to refresh not working when I have data in listView Android

I have a fragment with pull to refresh. Everything works fine when list view has no data, but, when it has at leas one row, it doesnt react when I swipe it down to refresh it. Here is the xml of the fragment:
<?xml version="1.0" encoding="utf-8"?>
<share.advice.khalifa.adviceshare.view.ListFragmentSwipeRefreshLayout
android:id="#+id/going_out_activity_swipe_to_refresh"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_going_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_event"/>
<ListView
android:id="#+id/going_out_activity_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/layout_padding"
android:background="#drawable/transparentna"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="none"
android:visibility="visible"/>
</RelativeLayout>
</FrameLayout>
</share.advice.khalifa.adviceshare.view.ListFragmentSwipeRefreshLayout>
And here is my custom ListFragmentSwipeRefreshLayout:
public class ListFragmentSwipeRefreshLayout extends SwipeRefreshLayout {
private ListView mListView;
public ListFragmentSwipeRefreshLayout(Context context) {
super(context);
}
public ListFragmentSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ListView getmListView() {
return mListView;
}
public void setmListView(ListView mListView) {
this.mListView = mListView;
}
/**
* As mentioned above, we need to override this method to properly signal when a
* 'swipe-to-refresh' is possible.
*
* #return true if the {#link android.widget.ListView} is visible and can scroll up.
*/
#Override
public boolean canChildScrollUp() {
final ListView listView = getmListView();
if (listView.getVisibility() == View.VISIBLE) {
return canListViewScrollUp(listView);
} else {
return false;
}
}
/**
* Utility method to check whether a {#link ListView} can scroll up from it's current position.
* Handles platform version differences, providing backwards compatible functionality where
* needed.
*/
private static boolean canListViewScrollUp(ListView listView) {
if (android.os.Build.VERSION.SDK_INT >= 14) {
// For ICS and above we can call canScrollVertically() to determine this
return ViewCompat.canScrollVertically(listView, -1);
} else {
// Pre-ICS we need to manually check the first visible item and the child view's top
// value
return listView.getChildCount() > 0 &&
(listView.getFirstVisiblePosition() > 0
|| listView.getChildAt(0).getTop() < listView.getPaddingTop());
}
}
}
And fragment code:
public class SuggestionGoingOutFragment extends Fragment implements ActivityAdapter.onContactsClickListener {
private Model mModel;
private OnActivityItemClicked mCallback;
public static ActivityAdapter mActivityAdapter;
private ListFragmentSwipeRefreshLayout mSwipeToRefresh;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_going_out, container, false);
mModel = Model.getInstance(getActivity());
mSwipeToRefresh = (ListFragmentSwipeRefreshLayout) view.findViewById(R.id.going_out_activity_swipe_to_refresh);
mSwipeToRefresh.setmListView(((ListView) view.findViewById(R.id.going_out_activity_list_view)));
mSwipeToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
mSwipeToRefresh.setRefreshing(true);
doUpdate(view);
}
});
initView(view);
return view;
}
}
you didnt provide details of what happens in doUpdate(view), anyways here's what i did in onRefresh().
#Override
public void onRefresh() {
refreshLayout.setRefreshing(true);
// create a handler to run after some milli seconds
// get data
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// call method for updated data for the list view
getlistviewdata();
// create new adapter with the new data
recyclerAdapter = new RecyclerAdapter(MainArrayList);
recyclerView.setAdapter(recyclerAdapter);
recyclerAdapter.notifyDataSetChanged();
refreshLayout.setRefreshing(false);
}
}, 2000);
}

Categories