I have a ScrollView which has about 13 EditText. What I am trying to do is converting speech to text,so when the user touch any EditText and click speak button and start speaking, it will convert to text for that EditText,and when he again touch another EditText it will convert to text for that EditText with out changing the previous EditText and so on.. I used for loop but the result of one speech is found in all the 13 EditText. I also add a break statement but didn't work well.
public void onResults(Bundle bundle) {
//getting all the matches
ArrayList<String> matches = bundle
.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
if (matches != null) {
for (int j = 0; j < speEdtId.length; j++) {
eEdit = findViewById(speEdtId[j]);
if (speEdtId[j]==speEdtId[0]) {
eEdit.setText(matches.get(0));
} else if (speEdtId[j]==speEdtId[1]) {
eEdit.setText(matches.get(0));
} else if (speEdtId[j]==speEdtId[2]) {
eEdit.setText(matches.get(0));}
.
.
.
break;
}
}}
You are setting the same value to every Edittext. what you can do is:
if (speEdtId[j]==speEdtId[0]) {
eEdit.setText(matches.get(0));
matches.clear();
}
And also set tag to every Edittext and find with tag within the loop. Hope this helps
It seems like you will have a lot of Speech EditText, I am not sure how you want your program to work with EditText so I will use TextView instead in my solution. In my opinion, it would be better and more efficient to use RecyclerViews than manually creating 13 EditText.
First I will create a Speech object. You can add as many variables you want like for example Timestamp of the speech
Speech.java
public class Speech {
private String speechInText;
private String speech;
public Speech(){
}
public Speech(String speechInText, String speech) {
this.speechInText = speechInText;
this.speech = speech;
}
public String getSpeechInText() {
return speechInText;
}
public void setSpeechInText(String speechInText) {
this.speechInText = speechInText;
}
public String getSpeech() {
return speech;
}
public void setSpeech(String speech) {
this.speech = speech;
}
}
Then, I would create an adapter to each of the speeches
SpeechAdapter.java
public class SpeechAdapter extends RecyclerView.Adapter<SpeechAdapter.ViewHolder> {
public static final String TAG = SpeechAdapter.class.getName();
public interface OnItemClickListener {
void onItemClick(Speech speech, int pos);
}
private List<Speech> speeches;
private AppCompatActivity parentActivity;
private final OnItemClickListener listener;
public SpeechAdapter(AppCompatActivity parentActivity, List<Speech> speeches, OnItemClickListener listener) {
this.parentActivity = parentActivity;
this.speeches = speeches;
this.listener = listener;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView speechInTextTV;
public ViewHolder(View v) {
super(v);
parentActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
speechInTextTV = itemView.findViewById(R.id.speechInTextTV);
}
});
}
public void bind(final Speech speech,final int pos, final OnItemClickListener listener) {
itemView.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onItemClick(speech, pos);
}
});
}
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.adapter_speech_list, parent, false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, int pos) {
final Speech speech = speeches.get(pos);
holder.speechInTextTV.setText(speech.getSpeechInText());
holder.bind(speech, pos, listener);
}
#Override
public int getItemCount() {
return speeches.size();
}
}
Next, I will create an XML file for the adapter
adapter_speech_list.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="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#color/white">
<TextView
android:id="#+id/speechInTextTV"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:padding="20dp"/>
</android.support.constraint.ConstraintLayout>
Finally, the Activity file or Fragment file
SpeechActivity.java
public class SpeechActivity extends AppCompatActivity{
public static final String TAG = SpeechActivity.class.getName();
private SpeechAdapter speechAdapter;
private List<Speech> speeches = new ArrayList<>();
private RecyclerView speechRV;
private int selectedSpeechPosition;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_speech);
speechRV = findViewById(R.id.speechRV);
//I add 13 text views manually here (with empty data)
for(int i = 0; i < 13; i++){
speeches.add(new Speech("",""));
}
speechAdapter = new SpeechAdapter(this, speeches, new SpeechAdapter.OnItemClickListener() {
#Override
public void onItemClick(Speech speech, int position) {
/*
you convert speech to text here
*/
selectedSpeechPosition = position;
}
});
speechRV.setNestedScrollingEnabled(false);
speechRV.setAdapter(speechAdapter);
speechRV.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
}
public void onResults(Bundle bundle) {
//getting all the matches
ArrayList<String> matches = bundle
.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
/*here you get the result of the converted speech and update the speeches list and then update the adapter to refresh the view*/
String convertedSpeech = "<PUT_YOUR_RESULT_HERE>";
speeches.set(selectedSpeechPosition,new Speech(convertedSpeech,"user's original speech or something else?"));
speechAdapter.notifyDataSetChanged();
}
}
and finally the xml file for the activity
activity_speech.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="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#color/white"
android:clickable="true">
<android.support.v7.widget.RecyclerView
android:id="#+id/speechRV"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
</android.support.constraint.ConstraintLayout>
This solution is only an example and my way of doing it if I am in your spot. You should study the code and manipulate it yourself to fulfill your own requirement.
Basically, you should pass in some kind of data into the speeches List and update the adapter in the onResult function and the recycler view will be refreshed with new data. When I am initializing the adapter in the activity file, I have implemented a OnItemClick function so when the user clicks on an item in the recycler view it will save the selected speech's position so that it in the onResult function, you can know which speech you should update (by using the selectedSpeechPosition)
Related
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?
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.
I have a RecyclerView implementing a LinearLayout of CardViews through an adapter. Inside each CardView, I have a spinner. What I need to do is get the CardViews position when a spinner is selected. Ex.. if I have 10 CardViews in a list on the screen with a spinner in each, and a select a value from the spinner in the 5th item, I need to get that 5th position as well as the selected value.
I'm able to get the selected value just fine. The issue is with getting the CardViews position. The CardViews are being generated from an ArrayList.
I will include my code below along with an image of the desired results. Any help is greatly appreciated!!
RecyclerView Adapter
public class PopularAdapter extends RecyclerView.Adapter<PopularAdapter.MyViewHolder> {
PopularFragment mPopularFragment;
private Context mContext;
private ArrayList<GameData> gameDataArr = new ArrayList<GameData>();
private String userId;
public PopularAdapter(Context context, ArrayList<GameData> gameDataArr, PopularFragment mPopularFragment, String userId) {
mContext = context;
this.gameDataArr = gameDataArr;
this.mPopularFragment = mPopularFragment;
this.userId = userId;
}
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView title;
public ImageView thumbnail;
private CardView mCardView;
PopularFragment mPopularFragment;
Spinner mGameSpinner;
LinearLayout mSpinnerLayout;
public MyViewHolder(View view, final PopularFragment mPopularFragment, final String userId) {
super(view);
this.mPopularFragment = mPopularFragment;
mSpinnerLayout = (LinearLayout) view.findViewById(R.id.spinner_layout);
title = (TextView) view.findViewById(R.id.item_title);
thumbnail = (ImageView) view.findViewById(R.id.item_main_img);
mCardView = (CardView) view.findViewById(R.id.item_cardview);
mCardView.setOnClickListener(this);
mGameSpinner = (Spinner) view.findViewById(R.id.game_spinner_options);
mGameSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
//String ASIN = gameDataArr.get(position).getAmazonId();
System.out.println(parent.getId()); // <--- prints the same value for each item.
if(userId == null){
Toast.makeText(mPopularFragment.getActivity(), "You must be logged in.", Toast.LENGTH_LONG).show();
return;
}
FirebaseDbHelper mFirebaseDbHelper = new FirebaseDbHelper();
if(position == 0){
// remove from db
// mFirebaseDbHelper.removeOwnedGame(ASIN, userId);
} else if(position == 1){
// add to owned games
// mFirebaseDbHelper.addOwnedGame(ASIN, userId);
} else {
// add to wishlist games
// mFirebaseDbHelper.addWishlistGame(ASIN, userId);
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
#Override
public void onClick(View view) {
System.out.println("click: " + getPosition());
//mPopularFragment.openGameActivity(getPosition());
}
}
#Override
public PopularAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
System.out.println("parent: " + parent);
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item, parent, false);
return new PopularAdapter.MyViewHolder(itemView, mPopularFragment, userId);
}
#Override
public void onBindViewHolder(final PopularAdapter.MyViewHolder holder, final int position) {
GameData game = gameDataArr.get(position);
holder.title.setText(game.getTitle());
Picasso.with(mContext).load(game.getFeatImgUrl()).resize(160, 200).into(holder.thumbnail);
}
#Override
public int getItemCount() {
return gameDataArr.size();
}
}
CardView
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/item_cardview"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginBottom="0dp"
card_view:cardCornerRadius="4dp"
>
<LinearLayout
android:padding="16dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/item_main_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_toLeftOf="#+id/right_content"
android:scaleType="fitXY"
android:adjustViewBounds="false"/>
<LinearLayout
android:id="#+id/right_content"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Spinner
android:id="#+id/game_spinner_options"
android:entries="#array/game_dropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></Spinner>
<Button
android:text="Buy Now"
android:id="#+id/game_buy_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
Popular Fragment
public class PopularFragment extends Fragment {
#BindView(R.id.popular_recyclerView)
RecyclerView mPopularRecyclerView;
private RecyclerView.Adapter mAdapter;
private ArrayList<GameData> gamesArray = new ArrayList<GameData>();
ApiResultsObject apiResults;
private FirebaseAuth auth;
private FirebaseUser user;
private FirebaseAuth.AuthStateListener authListener;
private String userId;
public PopularFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_popular, container, false);
ButterKnife.bind(this, view);
// bus instance
MyBus.getInstance().register(this);
// get api url
// trigger async task
// use results
String amazonApiUrl = getAmazonApiUrl();
if(amazonApiUrl != null){
new AmazonAsyncTask().execute(amazonApiUrl);
}
//get firebase auth instance
auth = FirebaseAuth.getInstance();
//get current user
user = FirebaseAuth.getInstance().getCurrentUser();
//add a auth listener
authListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
System.out.println("User logged in. Game activity.");
userId = user.getUid();
} else {
// User is signed out
System.out.println("User not logged in. Game activity");
}
}
};
// Inflate the layout for this fragment
return view;
}
private String getAmazonApiUrl() {
String amazonApiUrl = "";
AmazonQuery amazonQuery = new AmazonQuery("ItemSearch");
amazonApiUrl = amazonQuery.buildUrl();
return amazonApiUrl;
}
private void setData(ApiResultsObject data) {
gamesArray = data.getGamesArray();
if (data != null) {
mAdapter = new PopularAdapter(getActivity().getBaseContext(), gamesArray, this, userId);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mPopularRecyclerView.setLayoutManager(mLayoutManager);
mPopularRecyclerView.setAdapter(mAdapter);
}
}
#Subscribe
public void onAsyncTaskResults(BrowseAsyncTaskResult event) {
apiResults = event.getResults();
if (apiResults != null) {
setData(apiResults);
}
}
#Override
public void onDestroy() {
MyBus.getInstance().unregister(this);
super.onDestroy();
}
#Override
public void onStart() {
super.onStart();
auth.addAuthStateListener(authListener);
}
#Override
public void onStop() {
super.onStop();
if (authListener != null) {
auth.removeAuthStateListener(authListener);
}
}
}
You can set an OnClickListener on mGameSpinner in your onBindViewHolder().
onBindViewHolder(final PopularAdapter.MyViewHolder holder, final int position)
You can then store/use the position parameter as it will be the index into your gameArray for that particular spinner. You can then use that index to grab the spinner's currently selected value and do whatever you need to do with it.
I think you should set a tag for each view in the RV through the holder in onBindViewHolder. Something like:
holder.itemView.setTag(game.getId());
The game's Id should be one of the data points in the ArrayList you passed into the Adpater. So it'd be easier to add a getId() method to your game object.
Once you have an integer Id to call on that's unique to each game in the list, you can simply find that Id within the Spinner's onItemSelectedListener with:
int ID = (int) holder.itemView.getTag();
Then you can use ID within your if-else statements to know which game within your card list the spinner is selected for.
#Override
public void onBindViewHolder(final LanguagesAdapter.MyViewHolder holder, final int position) {
holder.edit_language_name.setText(edit_languageDetails_ArrayList.get(position).getEt_language_name());
sp_langage_rating=edit_languageDetails_ArrayList.get(position).getEt_language_ratings();
String ss=sp_langage_rating;
if(sp_langage_rating != null) {
if(sp_langage_rating.equals("Beginner"))
{
adapterLanguage = new ArrayAdapter<CharSequence>(context, R.layout.my_spinner_items, strarray_language);
adapterLanguage.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.sp_languages.setAdapter(adapterLanguage);
}
else if(sp_langage_rating.equals("Intermediate")) {
adapterLanguage = new ArrayAdapter<CharSequence>(context, R.layout.my_spinner_items, strarray_language1);
adapterLanguage.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.sp_languages.setAdapter(adapterLanguage);
}else if(sp_langage_rating.equals("Expert")){
adapterLanguage = new ArrayAdapter<CharSequence>(context, R.layout.my_spinner_items, strarray_language2);
adapterLanguage.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.sp_languages.setAdapter(adapterLanguage);
}
}else{
adapterLanguage = new ArrayAdapter<CharSequence>(context, R.layout.my_spinner_items, strarray_language);
adapterLanguage.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
holder.sp_languages.setAdapter(adapterLanguage);
}
Log.d("print","yes");
}
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...
I'm trying to make 'dynamic' listview that contains 0-2 headers and 0-1 footer(based on the needed mode).
If I use the order
getListView().addFooterView(footerView, null, true);
getListView().setAdapter(myAdapter);
I receive an error:
java.lang.NullPointerException
at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:778)
at android.widget.ListView.addFooterView(ListView.java:364)
at com.my.fragments.QuestionListFragment.updateList(QuestionListFragment.java:63)
and if I switch the lines to
getListView().setAdapter(myAdapter);
getListView().addFooterView(footerView, null, true);
no errors, but footer view doesn't appear on mode change (it appears on the next mode change, so I assume the code is working).
footerView is not null at this moment in the runtime
I can't figure out what is the problem here, but I really need headers and footers to be part of the listview(I can make it with a ScrollView and separate frames, but that won't do)
Update: A thing to add: data which is shown in the list is being loaded asyncronously and is stored in the separate static ArrayList. I use setAdapter(null) on the loading start and setAdapter(myAdapter) on complete to modify the ListView. Maybe the problem is here?
Update2
view_question_list_footer.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_marginTop="2dip" android:layout_marginBottom="2dip">
<TextView android:textAppearance="?android:attr/textAppearanceSmall" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:textStyle="bold" android:text="Load More Questions" android:gravity="center"
android:layout_gravity="center" android:lines="3" android:textColor="#android:color/white"></TextView>
</FrameLayout>
QuestionAdapter.java
public class QuestionAdapter extends BaseAdapter {
public class BrowseSettings {
public SortingMode sortingMode = SortingMode.SORT_BY_DATE;
public boolean isAnswered = true;
public int categoryFilter = -1;
public String textFilter = null;
}
private LayoutInflater inflater = null;
private BrowseSettings settings = new BrowseSettings();
private LoaderListener loaderListener = null;
public QuestionAdapter(Context context, LoaderListener listener) {
inflater = LayoutInflater.from(context);
loaderListener = listener;
}
private void loadData() {
QAData.questions.clear();
appendData();
}
public void appendData() {
new QuestionLoaderThread(settings.isAnswered, settings.sortingMode, settings.categoryFilter, settings.textFilter,
QAData.questions.getCount(), BaseRequest.DEFAULT_COUNT_VALUE, loaderListener).start();
}
public void applyCategoryFilter(int filter) {
settings.categoryFilter = filter;
loadData();
}
public void applyTextFilter(String filter) {
settings.textFilter = filter;
loadData();
}
#Override
public int getCount() {
return QAData.questions.getCount();
}
#Override
public Object getItem(int position) {
return QAData.questions.get(position);
}
#Override
public long getItemId(int position) {
return QAData.questions.get(position).getId();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
QuestionViewHolder viewHolder = null;
if(convertView == null) {
convertView = inflater.inflate(R.layout.view_question, null);
viewHolder = new QuestionViewHolder();
viewHolder.content = (TextView) convertView.findViewById(R.id.QuestionContent);
viewHolder.likes = (TextView) convertView.findViewById(R.id.QuestionLikeCounter);
viewHolder.question = QAData.questions.get(position);
convertView.setTag(viewHolder);
} else {
viewHolder = (QuestionViewHolder) convertView.getTag();
}
viewHolder.content.setText(Html.fromHtml(viewHolder.question.getFormattedQuestion()));
viewHolder.likes.setText(String.valueOf(viewHolder.question.getLikesCount()));
return convertView;
}
private class QuestionViewHolder {
private TextView content;
private TextView likes;
private QAQuestion question;
}
}
Try using getListView().addFooterView(footerView); instead.