How to get clicks on RecyclerView (NOT the children) - android

Is there any way to set an onClickListener on a RecyclerView?
I have a RecyclerView with some children in it, and setting an OnClickListener on the parent RecyclerView. However, the onClick doesn't fire when I click on that view. See sample code below -- we want to get clicks on the parent, NOT the children. In this scenario we don't care about clicks on the items.
I have tried doing setFocusable(false), setClickable(false), and setOnClickListener(null) on the children to no avail. In any case I don't think the children are stealing clicks from the parent, because when I click on the area where there is no children, the clicks don't register either.
package com.formagrid.hellotest;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
public class HelloActivity extends Activity {
private RecyclerView mRecyclerView;
private RecyclerAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new RecyclerAdapter(Arrays.asList("hi", "this", "is", "some", "text"));
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("patricia", view.toString());
}
});
}
#Override
protected void onDestroy() {
super.onDestroy();
}
#Override
protected void onPause() {
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
}
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.Holder> {
public class Holder extends RecyclerView.ViewHolder {
protected TextView textView;
public Holder(TextView itemView) {
super(itemView);
this.textView = itemView;
}
}
private List<String> contents;
public RecyclerAdapter(List<String> contents) {
this.contents = contents;
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
return new Holder(new TextView(parent.getContext()));
}
#Override
public void onBindViewHolder(Holder holder, int position) {
holder.textView.setText(contents.get(position));
}
#Override
public int getItemCount() {
return contents.size();
}
}
}

Is there any way to set an onClickListener on a RecyclerView?
No. That is, you can set an OnClickListener, but RecyclerView will never call it. RecyclerView intercepts all touch events, but never calls performClick(), which is how View invokes its listener.
You can, however, simulate an OnClickListener with an OnTouchListener and a GestureDetector. For the GestureDetector's listener, we can use a SimpleOnGestureListener, implementing just the onSingleTapUp() method.
class ClickListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapUp(MotionEvent e) {
Toast.makeText(HelloActivity.this, "Clicked", 0).show();
return true;
}
};
Then we just need to feed the GestureDetector the MotionEvents from an OnTouchListener, and check the return to decide whether to consume the event, so as to not interfere with scrolling, dragging, etc.
final GestureDetector detector = new GestureDetector(HelloActivity.this, new ClickListener());
mRecyclerView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(detector.onTouchEvent(event)) {
return true;
}
return false;
}
}
);
Please note that the above solution works with pretty much only a "simple" RecyclerView, like the one described and given in the question. If you're using more involved item handling, like drag and drop or swipe, we'll need to handle our gesture detection a little further up the touch event chain.
To do this, we can subclass RecyclerView and perform the detection in the dispatchTouchEvent() method. If a single tap is detected, we simply call performClick(), which will fire the RecyclerView's OnClickListener.
public class ClickableRecyclerView extends RecyclerView {
private final GestureDetectorCompat detector;
public ClickableRecyclerView(Context context) {
this(context, null);
}
public ClickableRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
detector = new GestureDetectorCompat(context, new ClickListener());
}
private class ClickListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapUp(MotionEvent e) {
performClick();
return true;
}
};
#Override
public boolean dispatchTouchEvent(MotionEvent e) {
detector.onTouchEvent(e);
return super.dispatchTouchEvent(e);
}
}
Just use this subclass in place of your regular RecyclerView, and set an OnClickListener on it as you normally would. Additional constructors may be necessary, depending on how you're instantiating this.

This is how I have done in Kotlin style
fun RecyclerView.enableClickListener(){
val gesture = object : GestureDetector.SimpleOnGestureListener(){
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
this#enableClickListener.performClick()
return super.onSingleTapConfirmed(e)
}
}
val detector = GestureDetector(this.context, gesture)
this.setOnTouchListener { v, event -> detector.onTouchEvent(event) }
}
And this is how to use it
yourRecyclerView.apply {
enableClickListener()
setOnClickListener {
// Do what you want ...
}
}
Enjoy :)

Here is a trick, It's open for comments.
You can have 2 children items in a FrameLayout. The first being the RecyclerView and the second a View
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
android:id="#+id/over_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
Since its a FrameLayout the View would be on top of the RecyclerView and you can set an onClickListener to the View and it would behave as if it was the RecyclerView that was clicked
over_view.setOnClickListener {
.....
}

a click event on RecycleView ? try like this:
//set android:descendantFocusability="blocksDescendants" on parent layout
//then setOnClickListener
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
**android:descendantFocusability="blocksDescendants"**
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>

Related

RecyclerView Adapter onBind method

I have in my MainActivity 3 RecyclerViews .
One of them in a bottom sheet and it is the main one (By Default the Bottom sheet is Open To Display this one ), in its adapter's onbind method I made an onClickListener so that I want when the user clicks on an item in it,
I want to go back to the main activity class to set To Start a method which it's rolled is to close the Bottom Sheet and set the data for the next recycling view (which will appear when the Bottom Sheet is closed)
..... The issue here is how to start this method from the onBind method's Listener and give it a parameter from this viewHolder as its name and some of its attributes
if there is something not clear please let me know
#Override
public void onBindViewHolder(#NonNull final ViewHolder viewHolder, final int position) {
viewHolder.categoryImage.setImageResource(mRowOfCategories.get(position).getCategoryImage());
viewHolder.categoryName.setText(mRowOfCategories.get(position).getCategoryName());
viewHolder.mCardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
}
Easiest:
You declare the method you want to call in your Activity. It has to be public:
public void method(){}
Then, in the Constructor of the Adapter, you pass a reference to the Activity as a parameter:
public MyAdapter(Activity activity){}
And, in the onBindViewHolder:
MyActivity mActivity=(MyActivity)activity;
mActivity.method();
You can do that easily.
Define custom interface
public Interface CustomEventListener
{
public void MyEventListener(String message); //you can change parameters
}
In your adapter class
public Adapter ......... {
private CustomEventListener listener;
public void setListener(CustomEventListener listener)
{
this.listener = listener;
}
//Your onBind
Override
public void onBindViewHolder(#NonNull final ViewHolder viewHolder, final int position) {
viewHolder.categoryImage.setImageResource(mRowOfCategories.get(position).getCategoryImage());
viewHolder.categoryName.setText(mRowOfCategories.get(position).getCategoryName());
viewHolder.mCardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (listener != null)
listener.MyEventListener("Message");
}
});
}
}
In your Activity when create Adapter add this code
public void InitAdapter()
{
yourAdapter = new Adapter(); // bloa bla bla
yourAdapter.setListener(new CustomEventListener() {
public void MyEventListener(String message)
{
// then do what you want
}
}
}
You need to use Listener for handling the click in your Adapter something like this:
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
then call the listener with:
#Override
public void onBindViewHolder(#NonNull final ViewHolder viewHolder, final int position) {
...
viewHolder.mCardView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// call the listener.
mListener.onItemClick(v, viewHolder.getAdapterPosition());
}
});
}
then when you're using the adapter, set the listener with something like this:
adapter.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(View view, int position) {
// do something with the view and position.
}
});
You want your recycleviews to be aware of each other. You'll need a class, maybe MainActivity, that will keep the selected value, also clear it upon request, in between recycleviews. Then you'll need that value to propagate the next recycleview. If you want to be efficient then you'll use one recycleview and swap the data in between selections, and animate it so it looks like a new recycleview is created.

Detect onClickListener or onLongClickListener on RecyclerView

I'm trying to detect from my mainActivity wheter the user does a normal click or a long press because depend of it I need to do one thing or another, and I have the object of my SQLite on my MainActivity and not in my Adapter, I do not know if it's better to detect it on the Adapter or in MainActivity.
My adapter is a normal one which has a ViewHolder and items on it, but I need to detect the click on the view not on an specific view on row, can you guide me how to?
I'd like to get like a callback that detects when user does one action and depends of it do one thing or another
EXPLANATION
On my main activity I have the private DatabaseHelper db; object so I'd like to do something like if he does onClick view the detail onLongClick show a dialog to delete it.
Hope you understand my issue.
You might want to use an interface for that purpose, like such:
Add this to your Custom Adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ItemClickListener mClickListener;
public void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}
public interface ItemClickListener {
void onItemClick(int position);
boolean onItemLongClick(int position);
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
itemView.setHapticFeedbackEnabled(true); // vibration feedback on long click
}
#Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(getAdapterPosition());
}
#Override
public boolean onLongClick(View view) {
if (mClickListener != null) mClickListener.onItemLongClick(getAdapterPosition());
return true;
}
}
}
And in your MainActivity implement the ItemClickListener and overwrite its methods:
public class MainActivity extends AppCompatActivity
implements MyAdapter.ItemClickListener {
#Override
public void onItemClick(int position) {
//TODO Add OnClick behaviour
}
#Override
public boolean onItemLongClick(int position) {
return false;
//TODO Add OnLongClick behaviour
}
}
You have to use Adapter for this purpose, additionally you have to use GestureDetector for long press, AlertDialog for confirmation message and handle requisite sqllite accordingly.
Follow the Android guide about input Events here.
TLDR; You should use OnClickListener and OnLongClickListener

dispatchTouchEvent in Fragment in Android

I am trying to get swipe up and swipe down gestures working in Fragment.
The same is working fine with activity. In Fragment, I have an issue with dispatchTouchEvent. How do I use dispatchTouchEvent in Fragment? Is there an equivalent way to achieve this?
#Override
public boolean dispatchTouchEvent(MotionEvent me)
{
this.detector.onTouchEvent(me);
return super.dispatchTouchEvent(me);
}
Fragments are attached to activity, not replacing activity. So you can still override dispatchTouchEvent in your fragment parent activity and pass any actions from there.
For example:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
MyFragment myFragment = (MyFragment) getFragmentManager().findFragmentByTag("MY_FRAGMENT_TAG");
myFragment.doSomething();
return super.dispatchTouchEvent(ev);
}
If your goal is to detect/handle swipe, add touch event listener on the fragment's view after creating the view.
You must dispatchTouchEvent in your parent activity like that
Add this code to parent activity:
private List<MyOnTouchListener> onTouchListeners;
#Override
protected void onCreate(Bundle savedInstanceState) {
if(onTouchListeners==null)
{
onTouchListeners=new ArrayList<>();
}
}
public void registerMyOnTouchListener(MyOnTouchListener listener){
onTouchListeners.add(listener);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
for(MyOnTouchListener listener:onTouchListeners)
listener.onTouch(ev);
return super.dispatchTouchEvent(ev);
}
public interface MyOnTouchListener {
public void onTouch(MotionEvent ev);
}
OnSwipeTouchListener:
public class OnSwipeTouchListener{
private final GestureDetector gestureDetector;
public OnSwipeTouchListener (Context ctx){
gestureDetector = new GestureDetector(ctx, new GestureListener());
}
private final class GestureListener extends SimpleOnGestureListener {
//override touch methode like ondown ...
//and call the impelinfragment()
}
public void impelinfragment(){
//this method impelment in fragment
}
//by calling this mehod pass touch to detector
public void onTouch( MotionEvent event) {
gestureDetector.onTouchEvent(event);
}
And add this code to fragment you like to dispach touch in it:
//ontouch listenr
MainActivity.MyOnTouchListener onTouchListener;
private OnSwipeTouchListener touchListener=new OnSwipeTouchListener(getActivity()) {
public void impelinfragment(){
//do what you want:D
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setting for on touch listener
((MainActivity)getActivity()).registerMyOnTouchListener(new MainActivity.MyOnTouchListener() {
#Override
public void onTouch(MotionEvent ev) {
LocalUtil.showToast("i got it ");
touchListener.onTouch(ev);
}
});
}
I use this method to get all swipe to right or left event in fragment without conflicting with other elem in page .unlike rax answer

Listen for animation end on the Android Gallery control

I have a standard Android Gallery control:
<Gallery
android:id="#+id/galArt"
android:spacing="10dp"
android:fadingEdgeLength="0dp"
android:unselectedAlpha="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
From which I listen to events with this code:
galArt.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
showMessageToast("Selected: " + pos);
}
public void onNothingSelected(AdapterView<?> arg0) {}
});
This works as I guess it is intended to: when I swipe the image, I get a toast telling me which image is now selected.
However, this toast appears before the image has stopped sliding, while the animation is still running. I want to take action after it has stopped sliding, to avoid interrupting the animation.
What can I listen to in order to get the notification after the animation is done?
Alright, I have found a solution. Note that this is merely a solution, and not necessarily the best solution.
My solution is to just ignore all the logical events (like onScroll or onAnimationEnd, since I couldn't get any of them to work anyway), and listen to changes in a child view's location. When the child view is stationary, the animation has ended.
An actual benefit of doing it this way is that this works for both dragging and flinging.
A problem is that the onItemSelected function will be called from another thread than your UI thread. Solve that by using your activity's runOnUIThread function, as shown in the example.
The way to listen for changes (note that this is not the regular onItemSelected function, but rather my own onItemReallySelected):
galArt.setOnItemReallySelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
_activity.runOnUiThread(new Runnable() {
public void run() {
//Do your stuff here ...
}
});
}
public void onNothingSelected(AdapterView<?> arg0) {
//... or here.
}
});
My implementation of the Android Gallery:
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Gallery;
public class ArtGallery extends Gallery {
OnItemSelectedListener _listener;
Timer _timer = new Timer();
public ArtGallery(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ArtGallery(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ArtGallery(Context context) {
super(context);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_UP){
setTimer();
}
return super.onTouchEvent(event);
}
private int _lastScrollX = Integer.MIN_VALUE;
private void setTimer() {
//Cancel existing tasks (if any), and create a new timer.
_timer.cancel();
_timer = new Timer();
//Schedule our animation check.
_timer.schedule(new TimerTask() {
#Override
public void run() {
//Just some value that will change while the animation is running.
int x = getSelectedView().getLeft();
if(_lastScrollX != x){
//Value has changed; save current value, and reset the timer.
_lastScrollX = x;
setTimer();
}else{
//The value hasn't changed during the last 50ms. That probably means that the animation has stopped.
fireOnSelected();
}
}
}, 50);
}
public void setOnItemReallySelectedListener(OnItemSelectedListener listener){
_listener = listener;
}
//This function is copied from the Android Gallery source code, and works exactly like the original one.
private void fireOnSelected() {
if (_listener == null)
return;
int selection = this.getSelectedItemPosition();
if (selection >= 0) {
_listener.onItemSelected(this, getSelectedView(), selection, getAdapter().getItemId(selection));
} else {
_listener.onNothingSelected(this);
}
}
}

Implementing my own OnTouchListener

I would like to create my own OnTouchListener. Then I would like to encapsulate it to a .jar file for making it reusable.
This is my specific OnTouchListener:
public class TouchableView extends View implements OnTouchListener{
myTouch t=null;
public TouchableView(Context context) {
super(context);
// Set KeyListener to ourself
this.setOnTouchListener(this);
}
public TouchableView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Set KeyListener to ourself
this.setOnTouchListener(this);
}
public TouchableView(Context context, AttributeSet attrs) {
super(context, attrs);
// Set KeyListener to ourself
this.setOnTouchListener(this);
}
public void setmyTouch(myTouch listener) {
t = listener;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){
t.downTouch();
return true;
}
return false;
}
public interface myTouch{
public abstract boolean downTouch();
}
}
This is how I'm trying to use it:
public class MyTouchImplement extends Activity implements myTouch{
/** Called when the activity is first created. */
TextView tv;
int i=0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv=(TextView) findViewById(R.id.tv);
TouchableView view = (TouchableView) findViewById(R.id.view);
view.setmyTouch(this);
}
#Override
public boolean downTouch() {
i++;
tv.setText(i+"");
return true;
}
}
I would like to make it work for every component that the OnTouchListener works with.
The following works for me. Please check and see if this helps. Please feel free to modify the constructor to suit your needs. For this test I used a linear layout with two TextView (txtX, txtY) fields and one GridLayout control.
MineSweeperOnTouch.java
public class MineSweeperOnTouch implements View.OnTouchListener {
private View gridLayout = null;
private TextView txtX = null;
private TextView txtY = null;
public MineSweeperOnTouch(View aGridLayout, TextView aTxtX, TextView aTxtY) {
this.gridLayout = aGridLayout;
this.txtX = aTxtX;
this.txtY = aTxtY;
}
public boolean onTouch(View v, MotionEvent event) {
txtTimeX.setText("X: " + String.valueOf(event.getX()));
txtY.setText("Y: " + String.valueOf(event.getY()));
return true;
}
}
MainActivity.java (code snippet only)
-------------------------------------
public class MainActivity extends Activity {
private MineSweeperOnTouch gridLayoutListener = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//custom code starts here
final View gridLayout = findViewById(R.id.gridLayout);
final TextView txtX = (TextView) findViewById(R.id.txtX);
final TextView txtY = (TextView) findViewById(R.id.txtY);
gridLayoutListener = new MineSweeperOnTouch(gridLayout, txtX, txtY);
gridLayout.setOnTouchListener(gridLayoutListener);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
You've created an awfully complicated web of dependencies that you should simplify. For example you shouldn't be passing around the activity object like that.
Also you when creating an Activity class you do not need to redefine the constructors. Using the super constructors is fine. What you do need to define are the onCreate onStart onPause onStop onDestroy methods. I highly suggest you read the Activity Documentation
A simpler implementation than what you have above, would be to get rid of your myTouch interface. Remove the implements OnTouchListener from the TouchableView class and create a OnTouchListener class inside your activity class.
It would look something like this:
public class MyTouchActivity extends Activity{
TouchableView tv;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TouchableView();
tv.setOnTouchListener(new MyOwnTouchListener());
}
class MyOnTouchListener implements OnTouchListener{
public boolean onTouchEvent(View v, MotionEvent e){
switch(e.getAction){
case (MotionEvent.TOUCH_DOWN)
MyTouchActivity.this.touchDown();
break;
}
}
}
public boolean touchDown(){
//touch down happened
}
}

Categories

Resources