I am building an app where publishers upload questions to database. Publishers can add as many questions they want, so i used a recycler view to handle that. When publisher clicks the button to add question a new layout is inflated as specified in my recyclerview adapter. TopicQuestionsAdapter is the name of my recycler view adapter
...
RecyclerView addTopicOfTheDayWhereDynamicLayoutsWillBeAdded;
TopicQuestionsAdapter topicQuestionsAdapter;
ArrayList<TopicQuestions> topicQuestionsList;
addTopicOfTheDayWhereDynamicLayoutsWillBeAdded.setHasFixedSize(true);
addTopicOfTheDayWhereDynamicLayoutsWillBeAdded.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
topicQuestionsList = new ArrayList<>();
...
addTopicOfTheDayAddQuiz.setOnClickListener(v29 -> {
...
// dynamically add questions layout on subsequent clicks
// here with an x button in each layout to remove the
// entire question layout
topicQuestionsList.add(new TopicQuestions());
topicQuestionsAdapter = new TopicQuestionsAdapter("Adapter", getContext(), topicQuestionsList);
addTopicOfTheDayWhereDynamicLayoutsWillBeAdded.setAdapter(topicQuestionsAdapter);
topicQuestionsAdapter.notifyItemInserted(topicQuestionsList.size());
}
});
Now the recycler view layout consists of four FABs to add the different types of questions and options, one textview and one image button to remove the whole view
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopicQuestionsViewHolder {
//R.layout.add_question_layout is the layout
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.add_question_layout, parent, false)
return TopicQuestionsViewHolder(itemView)
}
/*this is where my problem is i observed that whenever i
add a new question, the layouts i had already added to previous
views get cleared. Recycler view destroys states*/
override fun onBindViewHolder(holder: TopicQuestionsViewHolder, position: Int) {
// the textview to show this question number
holder.questionNumber.text = "${position + 1}."
// the image button to remove the whole view
holder.removeQuestion.setOnClickListener {
topicQuestions.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, topicQuestions.size)
}
holder.addField.setOnClickListener {
// need to fix this
when (holder.theFields.visibility == View.GONE) {
true -> {
holder.theFields.visibility = View.VISIBLE
holder.addField.setImageResource(R.drawable.close)
Log.wtf(TAG, "true")
}
false -> {
holder.theFields.visibility = View.GONE
holder.addField.setImageResource(R.drawable.add)
Log.wtf(TAG, "false")
}
}
}
// this button would add a question field to this view
holder.addQuestionField.setOnClickListener {
val lParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
lParams.topMargin = 16
val view = LayoutInflater.from(ctx).inflate(R.layout.add_question_field_to_question_layout, null)
view.layoutParams = lParams
holder.toAddViews.addView(view)
}
// this button would add an image question field to this view
holder.addImageQuestionField.setOnClickListener {
Log.wtf(TAG, "image question will be added here")
}
// this button would add toggle option to this view
holder.addToggleOption.setOnClickListener {
Log.wtf(TAG, "toggle option will be added here")
}
// this button would add check box option to this view
holder.addCheckBoxOption.setOnClickListener {
Log.wtf(TAG, "check box option will be added here")
}
}
as i had hinted earlier in my code whenever i click on the addTopicOfTheDayAddQuiz button which is supposed to add another view below the already exiting one(s) all child views inside the already existing views are cleared. Please what is a better approach to fix this issue?
Your Adapter is doing a lot of work in onBindViewHolder(): setting up lots of click listeners which in turn toggle the UI so the users can enter their question details without being bothered by UI elements they don't need.
BUT you do not seem to keep track anywhere of the selections the users made for each question.
AND each time the user adds a new question, you throw away the existing Adapter:
topicQuestionsAdapter = new TopicQuestionsAdapter("Adapter", getContext(), topicQuestionsList);
addTopicOfTheDayWhereDynamicLayoutsWillBeAdded.setAdapter(topicQuestionsAdapter);
Since you did not share enough code for me to debug your app and test my solution, I'll just sketch some ideas:
Instead of using a new Adapter each time a new question is created, just add the question to the list and then call (please note: topicQuestionsList.size() - 1)
topicQuestionsAdapter.notifyItemInserted(topicQuestionsList.size() - 1);
One other thing you need to do is keep track of the states of each list item (which Buttons etc. are visible, what is the content of EditTexts if there are any...). If the list gets longer and the users need to scroll, onBindViewHolder() will be called repeatedly even if you keep working with the same Adapter.
So you need a class - let's call it ListEntry - with fields to reflect the possible states of a list item. And inside the Adapter, you can keep a List<ListEntry> with one ListEntry for each item of your data list. Each time some Button becomes visible, you update the corresponding ListEntry. In onBindViewHolder() you evaluate the ListEntry for the given position and set the attributes of the child Views of holder.itemView accordingly.
Related
I'm working on android app that creat layout with textView & checkbox programmatically for each text user input in EditText, and when the user select one of the checkbox and click on delete button the layout that contain that checkbox remove from the main layout
public void plusBtn(View view)
{
item = actv.getText().toString(); // text from EditText
actv.setText("");
creatt();
}
public void deletBtn(View view)
{
if(chbox.isChecked()){
linear.removeView(linr);
}
}
public void creatt()
{
linr = new LinearLayout(this);
linr.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
linr.setOrientation(LinearLayout.HORIZONTAL);
linr.setGravity(Gravity.RIGHT);
TextView txt = new TextView(this);
txt.setText(item);
linr.addView(txt);
chbox = new CheckBox(this);
linr.addView(chbox);
linear.addView(linr); // main layout
}
But when I click on delete button just the last layout removed, and that not what I want.
though the technique you're using is not fine. but let me give you a shortcut. in your deletBtn method try to get the reference of the desired deleted view (try to handle case of id or boolean you've to make logic) and enclose this line linear.removeView(linr); into that condition.
Summery, only delete the view which meets the condition.
Alternative, do your whole task from scratch with the help of Recycler-view. as it Recycler-view will give you exact location of cell.
Imagine a gallery application (sort of).
But instead of gallery I want to present a choice in form of 10 images displayed onto the screen.
How should you detect the one that user has clicked on?
What is a best way to implement this? Should I use ImageView and onClick method?
Imagine implementing onClick event for a 100 ImageViews?
?for every ImageView displayed onto the screen check if it contains user touch coordinates?
Same question bothers me for how to detect if the user has touched a Bitmap drawn onto a canvas.
Java, Android.
You're going to want to use a RecyclerView with an ImageView in your list item's layout xml.
You can create a clickable ViewHolder like this:
class ClickableViewHolder(final override val containerView: View, onClick: (position: Int) -> Unit) : RecyclerView.ViewHolder(containerView) {
init {
containerView.setOnClickListener {
val pos = absoluteAdapterPosition
// check if item still exists
if (pos != RecyclerView.NO_POSITION) {
onClick(pos)
}
}
}
}
Usage in your Adapter class:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return ClickableViewHolder(v) { position: Int ->
getItem(position)?.let {
//Do something here
}
}
}
I have activity one where having a empty textview user has click on this textview to select location from list of location so for that when user click on select location textview it will open list of location with checkbox.
When user select location(can select multiple location) and click on done then all selected location will be showing on activity one textView with all selected checked textview value now when user click on same textview to add more location then on recylerview list all previous checked item should be checked. I'm not getting all previous selected checkbox.
I'm not getting how to achieve this. I need all old checkbox should be selected and user can select some more new checkbox if click on same textview. Please help me to get this. Java code will be also helpful for me
Below is my recylerView Adapter code:-
class SelectMedicineAdapter (val medicineList : ArrayList<String>, val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var selectedCheckBoxMedicineList : ArrayList<String> = ArrayList()
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.textViewSelectMedicineName.text = medicineList.get(position)
holder.itemView.checkboxSelectMedicine.setOnCheckedChangeListener { buttonView, isChecked ->
val itemText = medicineList.get(position)
if (isChecked) {
selectedCheckBoxMedicineList.add(itemText)
} else {
selectedCheckBoxMedicineList.remove(itemText)
}
}
}
fun getSelectedMedicineList(): ArrayList<String> {
return selectedCheckBoxMedicineList
}
override fun getItemCount(): Int {
return medicineList.size
}
override fun onCreateViewHolder(holder: ViewGroup, p1: Int): RecyclerView.ViewHolder {
val v= (LayoutInflater.from(context).inflate(R.layout.row_select_medicine_adapter,holder,false))
return ViewHolder(v)
}
class ViewHolder (itemView: View): RecyclerView.ViewHolder(itemView){
var textViewSelectMedicineName = itemView.textViewSelectMedicineName
var imageViewPlusButton = itemView.imageViewPlusButton
var imageViewMinusButton = itemView.imageViewMinusButton
var checkboxSelectMedicine = itemView.checkboxSelectMedicine
}
}
You need to update views with their items state (selected or not).
In onSaveInstanceState of your activity/fragment where your adapter is you should write adapters state (which items are selected (getSelectedMedicineList)) to the bundle.
Whenever your fragment/activity is restored just update adapter with data you saved restoreSelectedMedicineList(selectedCheckBoxMedicineList: ArrayList<String>)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.textViewSelectMedicineName.text = medicineList.get(position)
holder.itemView.checkboxSelectMedicine.setOnCheckedChangeListener(null)
if(selectedCheckBoxMedicineList.contains(itemText)) {
holder.itemView.setChecked(true)
} else {
holder.itemView.setChecked(false)
}
holder.itemView.checkboxSelectMedicine.setOnCheckedChangeListener { buttonView, isChecked ->
val itemText = medicineList.get(position)
if (isChecked) {
selectedCheckBoxMedicineList.add(itemText)
} else {
selectedCheckBoxMedicineList.remove(itemText)
}
}
}
fun restoreSelectedMedicineList(selectedCheckBoxMedicineList: ArrayList<String>) {
this.selectedCheckBoxMedicineList = selectedCheckBoxMedicineList
notifyDataSetInvalidated()
}
When you start an Activity, it has no knowledge of what is the state of your data. You need to provide the state for the Activity.
By which I mean when the user selects his/her desired locations and goes back to Activity One you to need hold on to these selected locations and when the user again wants to update the locations you need to pass these previously selected locations to the second Activity and then update the RecyclerView's backing data accordingly.
in your list you do maintain a boolean field when select check box then selected position value is true, and notify data and inside onbind viewholder you check first which position is true. if true then show selected checkbox otherwise unselect.
When I'm working with an app, I have faced same problem and also I needed that checked boxes even app closes. So I used SharedPreferences to stored and retrieve values that will indicates states of check boxes then I can easily specify the states of all element. So if you face same problem and have no solution you can use this way.
Just update the model class with a flag on every tick and untick. Check this flag to tick and untick logic for restoring the checkbox state.
I need some help here. I am fetching the data from the JSON.Fetching data. It consists of images and text and I am using the Fast Adapter(mike penz) to populate into recycler view, but when I select the specified row from the recycler view, it needs to change the image color. Where can I change the text view color by using the selector but I can't change the color of the image in the image view of the selected row. Please help me out. Here is the code:
service_type_adapter.withOnClickListener(new FastAdapter.OnClickListener<Service_Type_Adapter>() {
#Override
public boolean onClick(View v, IAdapter<Service_Type_Adapter> adapter, Service_Type_Adapter item, int position) {
AppCompatImageView service_image= (AppCompatImageView) v.findViewById(R.id.service_image);
int service_imagecolors = ContextCompat.getColor(getApplicationContext(), R.color.skyblue);
service_image.setColorFilter(service_imagecolors, PorterDuff.Mode.SRC_IN);
service_type_adapter.select(position);
if (lastselectedposition != -1) {
service_type_adapter.deselect(lastselectedposition);
}
lastselectedposition = position;
servicetypename = item.getServicename();
action = item.getServiceid();
googlemap.clear();
onMapReady(googlemap);
return true;
}
});
The FastAdapter's OnClickListener already provides you everything you need. It passes the clicked item, and also the Adapter responsible for the specific item.
So when the user clicks on the item (and you have enabled selection for that item) the FastAdapter will automatically set the state of that item to selected.
There are multiple ways of automatically applying the color in that case:
Use ColorStateList
The easiest solution is to define a ColorStateList instead of a simple color for that item. For example you could have a Foreground and just define a translucent color if the state is selected.
A very simple ColorStateList could look like this:
return new ColorStateList(
new int[][]{
new int[]{android.R.attr.state_selected},
new int[]{}
},
new int[] {
colorSelected,
Color.TRANSPARENT
}
);
Automatically Notify the Adapter about the change
The FastAdapter allows you to enable the bindView being called in case of the selection. Enable this via: FastAdapter.withSelectWithItemUpdate(true) After this the bindView method of that element is called and you can simply check for isSelected and define the ColorFilter or not
Notify the Adapter manually
If the adapter should not automatically call the notify method you can do this on your own also by doing fastAdapter.notifyAdapterItemChanged(position) (you can optional pass a payload in addition, so you can check for that one in the bindView method) after that check again in the bindView method for isSelected or not and handle the UI as you need it
I have a ListView with fastscroll enabled:
mList.setFastScrollEnabled(true);
This works very well. But when I change the adapter, I cannot fast scroll anymore.
mList.setAdapter(mAdapter);
My adapter has getCount() properly implemented. Calling
mList.setFastScrollEnabled(true);
after setting the new adapter doesn't work either nor adding android:fastScrollEnabled="true" to the ListView XML works.
Is there any way to reenable fast scrolling?
After digging in the depths of the Android sourcecode, I finally found a solution:
mAdapter = new ArrayAdapter....
mList.setAdapter(mAdapter);
// We have to post notifyDataSetChanged() here, so fast scroll is set correctly.
// Without it, the fast scroll cannot get any child views as the adapter is not yet fully
// attached to the view and getChildCount() returns 0. Therefore, fast scroll won't
// be enabled.
// notifyDataSetChanged() forces the listview to recheck the fast scroll preconditions.
mList.post(new Runnable() {
#Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
FastScroller.java has the following method:
public void onItemCountChanged(int totalItemCount) {
final int visibleItemCount = mList.getChildCount();
....
updateLongList(visibleItemCount, totalItemCount);
}
When setAdapter() is called, FastScroller re-checks the conditions for enabling fast scrolling. But since the new adapter is not fully shown yet, mList.getChildCount() returns zero and updateLongList() won't enable fast scrolling.
With my fix, the ListView gets notified that the underlaying data was changed after the new adapter was fully attached to the view and the preconditions for fast scrolling are now satisfied.