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
});
Related
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
I have a cardview, behind which there is a recyclerview of "Suggested Contacts", much like the default dialer in android. The CardView is placed to the bottom of the screen, and occupies about 70% of the screen, while the RecyclerView extends its height to match_parent. When the RecyclerView is scrolled, the CardView disappears and reveals the whole list.
The user can click on one of the RecyclerView's items to make a call directly. The issue is that items behind the CardView, which are not visible to the user, are also clickable.
How can I have the RecyclerView items which are visible only as clickable? (i.e. while the cardView is visible)
xml code:
<ScrollView
android:id="#+id/sv_suggested_contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="expandSuggestions">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_suggested_contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
<androidx.cardview.widget.CardView
android:id="#+id/dialer_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:cardElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
[...]
I'm assuming your requirement is that the cardview should block all clicks, so that items below don't take the click right.
If that is the case and you are sure cardview is on top of the recyclerview you simply need to set an onClickListener on your cardview and do nothing inside it. Basically an empty click.
Or you can in xml for cardview add
android:onClick="nullClick"
and in the activity create the empty function
public void nullClick(View view) {
}
whichever way you like.
you can hide (setVisibility(View.GONE) item behind recycler, or set them not clickable
I have a LinearLayout with a couple of TextViews stacked vertically. I want to attach a click listener to the parent and make the click event propagate to one of the TextViews (child views of the containing LinearLayout).
I understand that I can attach click listener's to the individual TextViews and achieve the same result but I'd like to do otherwise.
I came across the
android:duplicateParentState="true"
attribute, but this doesn't seem to solve the issue either. What should I be doing to get the child views to consume the containing parent's click event. Also each text view should perform a different action.
Here is the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text_1"
android:duplicateParentState="true"
android:layout_marginBottom="10dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:text="Text_2" />
</LinearLayout>
And here is the Activity:
public class MainActivity extends AppCompatActivity {
LinearLayout mLinearLayout;
TextView mTextView1;
TextView mTextView2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLinearLayout = (LinearLayout) findViewById(R.id.linear_layout);
mLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.v("view",view.toString());
}
});
}
}
The log always prints the parent view:
V/view: android.widget.LinearLayout{3ea32ab0 V.E...C. ...PH... 0,0-1080,1533 #7f0c0050 app:id/linear_layout}
V/view: android.widget.LinearLayout{3ea32ab0 V.E...C. ...PH... 0,0-1080,1533 #7f0c0050 app:id/linear_layout}
I am obviously missing something here and hopefully the solution doesn't involve navigating down the view stack. What am I doing wrong, any help would be most appreciated.
You may use setOnTouchListener for your LinearLayout. Inside this OnTouchListener you need to detect a click (GestureDetector can help) in this point you will have MotionEvent, you can get x,y coordinates from this event and compare them with your TextViews coordinates. So you can detect which TextView was clicked. In such a way you can do what you want, but it is complicated, and I believe you can avoid it.
I have a listview for my android app and I want to make an animation at the top of the list view when the user over scrolls like one the one for the instagram or twitter app. How would I go about this. Is there a method that is called when the user over scrolls and I was thinking about using a frameanimation for the actually animation.
You can add a swipe refresh layout on your xml file:
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/favs_refresher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<ListView
android:id="#+id/favs_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
</android.support.v4.widget.SwipeRefreshLayout>
Then on your activity:
swipeRefresher = (SwipeRefreshLayout)findViewById(R.id.favs_refresher);
swipeRefresher.setOnRefreshListener(this);
Be sure that your activity implements SwipeRefreshLayout.OnRefreshListener
then you can make use of the onRefresh() method:
#Override
public void onRefresh() {
//do something
}
I have a listview containing a button and a textview the textview is being clicked normally; but I have a method for the button click, and when the button is clicked it should change the background image, so by clicking a button all the instances of the button are changing the background image, so I click on fiew buttons in the listview and I scroll down and I see that several button have their background image changed than the place of the buttons with the changed background changes while scrolling the listview up again;
how can I fix that?
the code of the content of my listview:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<Button
android:id="#+id/bt_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:background="#android:drawable/btn_star_big_off"
android:onClick="myClickHandler"/>
<TextView
android:id="#+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="#dimen/fsinlistview"
/>
</LinearLayout>
the code of my button click is:
public void myClickHandler(View v)
{
Button button = (Button)v.findViewById(R.id.bt_rating);
button.setBackgroundResource(android.R.drawable.btn_star_big_on);
}
You're specifying the background resource for all buttons with the R.id.bt_rating id, where as you'd like to modify the button that was clicked.
You already have a reference to that - it's the View that is passed in.
Change your code to:
public void myClickHandler(View v)
{
v.setBackgroundResource(android.R.drawable.btn_star_big_on);
}
Of course, since you're using a list view, once you scroll through/past this, it won't persist state. What you should be doing is:
updating the data backing your adapter
call notifyDataSetChanged on the adapter
handle the button on/off state display in the adapters getView
Put the setOnclickListener in your getView method of your adapter