I am new to android development. For my learning purpose, I am developing an android application to list the fibonacci numbers in a recycler view. The list gets appended with new numbers as the user scrolls down the recycler view.
The image shows the app displaying the index and respective fibonacci number for the index in the recycler view
This is the layout xml of single item in recycler view.
<?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="wrap_content"
android:orientation="horizontal"
android:clickable="false"
android:focusable="false"
android:padding="5dp">
<TextView
android:id="#+id/info_index"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
<TextView
android:id="#+id/info_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
</LinearLayout>
Excerpt from the adaptor class,
public class ViewHolder extends RecyclerView.ViewHolder {
final TextView index;
final TextView value;
ViewHolder(View itemView) {
super(itemView);
index = itemView.findViewById(R.id.info_index);
value = itemView.findViewById(R.id.info_value);
}
}
public void onBindViewHolder(#NonNull ViewHolder holder, final int position) {
final TextView value = holder.value;
final TextView index = holder.index;
value.setOnClickListener(new View.OnClickListener() {
#RequiresApi(api = Build.VERSION_CODES.KITKAT)
#Override
public void onClick(View v) {
if (value.getMaxLines() == 1000) {
value.setMaxLines(1);
notifyItemChanged(position);
} else {
value.setMaxLines(1000);
notifyItemChanged(position);
}
}
});
Log.d(TAG, "position-value:" + String.valueOf(position));
holder.index.setBackgroundColor(Color.WHITE);
holder.index.setText(String.valueOf(position + 1));
holder.value.setBackgroundColor(Color.WHITE);
holder.value.setText(mData.get(position));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
holder.index.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
holder.value.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
}
}
When I click on the value textview, the textview does not expand on the first click, instead I have to press two times to make it expand. The first time only the flickering happens. I have tried disabling the animator for recyclerview, tried re using the same view in the recycler view, but nothings helps.
My requirement is to expand fibonacci-value textview on click. By default it should be 1 line and when clicked it should show the whole content with multiple lines (as many as required).
currently this happens with two clicks. first time flickering and second time expands.
I believe this is a bug in android code. But just want to confirm here for any solutions that I might have missed.
The reason of the flickering is that, each time an item is clicked, just after setting maxLines, you are calling notifyItemChanged (which is the correct thing to do), but as a result, before redrawing the item onBindViewHolder is called again. So, when it is called again, there should be a way to know current max lines for that item.
Besides, if you try adding lots of items and scroll up and down, you'll see more bugs (since viewholders are reused) Thus, it is important to set/reset maxlines for each item inside onBindViewHolder (but outside click listener)
Secondly, DefaultItemAnimator of RecyclerView uses cross-fade animation when an item changes and by default, it creates two viewholders for that position for cross-fading between the two. So, above, you set a clicklistener on your "value" textview and interfere with the textview inside onClick callback. However, when you later click and inform adapter that the item is changed, it binds the second viewholder instance. So when you click, your click is consumed with the previous "value" instance, and right afterwards a new instance is bound and you set a new clicklistener to this second viewholder instance.
This is one of many reasons that interfering with viewholder items inside the click listener is error-prone. Sometimes people solve this kind of problems with setTag/getTag but I think it is similarly error-prone as well.
I think the easiest solution is to use a POJO (plain old java object) for each item and include the maxLine state in this POJO. Something like FibonacciItem with fields such as int: index, String: fibonacciNumber, boolean : expanded. Then you will provide the list as a list of FibonacciItems. And inside your click listener, you'll update maxlines of the clicked item. Something like this:
public void onClick(View v) {
if (mList.get(position).isExpanded()) {
mList.get(position).setExpanded(false);
notifyItemChanged(position);
} else {
mList.get(position).setExpanded(true);
notifyItemChanged(position);
}
}
And inside your onBindViewHolder, (but outside your click listener) you should set max lines for each item according to this value:
if(mList.get(position).isExpanded(){
holder.value.setMaxLines(1);
} else {
holder.value.setMaxLines(1000);
}
This will solve the issue. Besides, we usually use POJOs (or data classes) for each item in a list. It is easier to manage.
I was able to repeat your issue on my device, the issue you have is that your LinearLayout is consuming your click before it reaches your textview.
Include:
android:clickable="false"
android:focusableInTouchMode="false"
android:focusable="false"
Within your LinearLayout like so:
<?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="wrap_content"
android:orientation="horizontal"
android:clickable="false"
android:focusableInTouchMode="false"
android:focusable="false"
android:padding="5dp">
<TextView
android:id="#+id/info_index"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
<TextView
android:id="#+id/info_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#CDD6D5"
android:gravity="center"
android:maxLines="1" />
</LinearLayout>
Also include:
android:nestedScrollingEnabled="false"
Within your RecyclerView
Related
I need to make day selector like in the screenshot below.
The problem is in coloring numbers. I need white numbers while they are in the circle.
I decided to do it with with 2 recyclerviews that are synchronized with each other. One recyclerview has black colored items and the other has white ones.
But there is no way to combine them together to get the desired result. Please, give me an idea of the correct implementation of this.
If it was possible to give the recyclerview view a shape of a circle, it would resolve my problem. But it seems that recyclerview doesn't have this feature.
Yes you can give recycler view items's shape circle by these steps
1: Create a framelayout
2: Place cardview inside it.
3: Add app:cardCornerRadius="150dp" in card view.
4: Add textview inside
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="4dp"
>
<androidx.cardview.widget.CardView
android:padding="10dp"
android:layout_margin="1dp"
android:id="#+id/crd"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="150dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:text="4"
android:textColor="#000"
android:gravity="center"
android:layout_centerVertical="true"
android:id="#+id/colorBox"
/>
</androidx.cardview.widget.CardView>
</FrameLayout>
output would b like :
Output
use Model to RecyclerView and handle state inside it. make one boolean into Model and If date gets selected make it true else make it false and notify adapter. Inside adapter, if the flag is true then make date TextBox colour white else black
class Model {
String date;
boolean isSelected;
//....
public boolean isSelected(){
return isSelected;
}
public void setSelected(boolean isSelected){
this.isSelected = isSelected;
}
}
inside onBindViewHolder() of adapter
if (modelList.get(position).isSelected())
holder.textBoxDate.setTextColor(ContextCompat.getColor(context, R.color.white));
else
holder.textBoxDate.setTextColor(ContextCompat.getColor(context, R.color.black));
for more google it "how to manage selected state in recyclerview android"
I have a LinearLayout with a nested RecyclerView showing a list of items. I'd like to open a popup when RecyclerView is clicked (either one of the items or the background white area), but the usual setOnClickListener is not working.
Of course I can put a click listener on each of the items, but the white area between them remains unclickable.
Is there a way to make the entire RecyclerView area clickable?
EDIT: I've added some sample code. I'd like to have the entire layout clickable to open a popup, but while the first three views behave properly, the RecyclerView does not.
<LinearLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="#dimen/spacing_half"
android:background="#color/color_item_margin_divider"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/fragment_tags_title"
style="#style/ItemFragmentHeader"/>
<View
android:layout_width="match_parent"
android:layout_height="#dimen/spacing_line"
android:background="#color/color_line_divider"/>
<RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/spacing_half"/>
</LinearLayout>
Add onClickListener in the viewHolder . Below is a snippet of my project where I had implemented Listener
public class MyViewHolder extends RecyclerView.ViewHolder {
public ImageView shotThumbnail;
public MyViewHolder(View view) {
super(view);
shotThumbnail = (ImageView) view.findViewById(R.id.shotThumbnail);
shotThumbnail.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Put here stuff that you want the onclickListener should do
}
});
Check if this link helps:
Detect click on RecyclerView outside of items
You should use padding instead of margin.
<RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_padding="#dimen/spacing_half"/>
Cause whenever you use padding it will crop your RecyclerView. But whenever you use padding it will just shorten your inside functions. hence you need padding. Then you should use an onClickListener in MainActivity.
recyclerView.setOnClickListener(v->{
//Do whatever you want
});
I have a custom ListView item that has an ImageButton in it, and when I click it, the click events don't come through until the list item as a whole is interacted with, such as scrolling the list or tapping somewhere else on the list item. So for instance, if I click the ImageButton 5 times, nothing will happen, but then when I scroll the list, all 5 click events will come through simultaneously.
After a lot of research, I've come to find this is a common question, but none of the solutions I've found have worked for me so far. This question was quite helpful in learning some of the quirks of view interaction, and most other solutions I found used a similar approach to the accepted answer on that question, but unfortunately none of them worked for my particular situation.
So what I need to happen is to handle the click events for the ImageButton on the list item, as well as the click event for the list item itself. I've tried setting android:focusable="false" and android:focusableInTouchMode="false" on pretty much every view element involved in the display of this list. I've also tried setting these attributes programmatically. I also tried setting android:descendantFocusability="blocksDescendants" on several view elements including the ListView, the LinearLayout of the row item, and the CardView that contains all of this stuff.
The confusing part about all of this, is that I have this working just fine in another Activity of the same app. I have an ImageButton in a custom row layout of a ListView, and the onClick events work just fine for that. So it has to be something with my setup on this Activity. Here's some code:
The getView() of my custom BaseAdapter:
public View getView(int position, View convertView, ViewGroup parent) {
FavoritesItemViewHolder favoriteItem = favoritesList.get(position);
if(convertView == null) {
convertView = favoriteItem.getNewConvertView(parent);
} else {
if(favoriteItem.getConvertView() == null) {
convertView = favoriteItem.getNewConvertView(parent);
} else {
convertView = favoriteItem.getConvertView();
}
}
return convertView;
}
The view inflation of my individual view holders:
public View getNewConvertView(ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.favorites_list_item, parent, false);
TextView favoriteText = (TextView) convertView.findViewById(R.id.favoritesItemText);
favoriteText.setText(itemTitle);
//Here is where I'm setting the OnClickListeners for each row...
ImageButton clearButton = (ImageButton) convertView.findViewById(R.id.favoritesClearButton);
clearButton.setVisibility(View.VISIBLE);
if(itemTitle.equals(context.getResources().getString(R.string.no_favorites))) {
clearButton.setVisibility(View.INVISIBLE);
} else {
clearButton.setVisibility(View.VISIBLE);
clearButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("CLEAR FAVORITE", "The clear button was just clicked on a favorites item...");
}
});
}
return convertView;
}
And here is the XML for my custom row layouts...
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/favoritesListItemContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/favoritesItemText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_gravity="center_vertical"
android:textSize="16sp"
android:textColor="#android:color/black"
android:layout_weight="0.9"/>
<ImageButton
android:id="#+id/favoritesClearButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:src="#drawable/ic_clear_x"
android:layout_gravity="center_vertical"
android:scaleType="centerInside"
android:adjustViewBounds="true"
android:background="#android:color/white"
style="?android:attr/borderlessButtonStyle"
android:layout_weight="0.1" />
</LinearLayout>
Any help would be appreciated. I'm just completely stumped here.
UPDATES
In this Activity, I'm using getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); in order to disable a SearchView and some other UI, and when the SearchView receives focus, suddenly my ListView items work as intended. So I'm guessing this has an issue with the ListView or the Window it's in having no focus, and so the click events don't come through until something on the screen receives focus.
What's strange is, when the keyboard is showing on the screen, I can click the list items and their ImageButtons and all the click events are handled correctly. But when I dismiss the keyboard, it breaks again.
There are known problems with ListView interfering with the touch events of views inside each item. You should switch to RecyclerView instead. ListView is seen as essentially deprecated for new apps with RecyclerView becoming its replacement.
I am programming my first android app. For test purposes I show a list of objects (class cElement) at the mainactivity.
public class cElement {
private Integer Id;
private String Name;
private String Description;
private Boolean Checked;
....
}
The ListView is linked with a custom adapter to the list. The custom adapter extends the BaseAdapter and I inflate the layout of each element from the list.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#+id/standard"
android:textSize="20dp"
android:layout_weight="1">
</TextView>
<TextView
android:id="#+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#+id/standard"
android:textSize="20dp"
android:layout_weight="2">
</TextView>
<TextView
android:id="#+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#+id/standard"
android:textSize="20dp"
android:layout_weight="3">
</TextView>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:id="#+id/checkBox"/>
</LinearLayout>
First question: Now I would like to check all CheckBoxes in one step with a button. What is good style?
I update the list directly and call the notifyDataSetChanged method of the adapter
I create a method checkall() within the adapter and call it from outside.
Second question: I would like to know which CheckBox is checked. So I create an eventhandler setOnClickListener and save the position in a variable within the adapter. Is this the right way?
First question: Now I would like to check all CheckBoxes in one step with a button. What is good style?
As it seems you have List which contains boolean Checked for status, you should update all objects of list and notify list again on click of checkAll button.
Second question: I would like to know which CheckBox is checked. So I create an eventhandler setOnClickListener and save the position in a variable within the adapter. Is this the right way?
You should implement listener from fragment/activity, when user clicks on any check box, from adapter, you will get click event in fragment/activity, over there, you update your list item for checked status.
So, in your fragment/activity:
new View.OnClickListener() {
#Override
public void onClick(View v) {
v.getTag()
}
}
And in adapter, set tag for each item with item id/ item object, so when user clicks on any item, you will come to know that item object / item id.
I've got a ListView with several rows. Every row consists of 7 TextViews which react to onClick() events.
This all works perfectly fine but when the user clicks on the margins of the row, where no TextView catches the onClick() event, the root view - a LinearLayout - get's highlighted.
This is normal behaviour of course but as nothing happens by clicking there I don't want the Linear Layout to be highlighted.
Is there any way of disabling this behaviour but keep catching the onClick() events on the TextViews?
(The onClick listener is set inside the adapters getView() method)
Here some extract of the xml file. As one can see I've tried some things but they don't work.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:longClickable="false"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false">
<TextView
android:id="#+id/listelement_weekoverview_tv_mo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:layout_weight="1"
android:background="#drawable/weeklylist_rndrectangle"
android:gravity="center_horizontal"
android:singleLine="false"
android:textColor="#color/appcolor_red_base" />
...
</LinearLayout>
unclicked version
clicked version
I've found a solution.
Simply overwrite the isEnabled (int position) method in your custom Adapter like this:
#Override
public boolean isEnabled (int position) {
return false;
}