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
Related
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>
is there a way to create a listener that activates an event under a certain condition(boolean)?
i tried reading about creating custom listeners using interfaces but i dont think it's the answer for my question.
right now in my app i write an if statement everywhere so if i could just create a listener for it, it would be much easier.
set_A==B_Listener(????? {//listener takes place if a==b
#Override
public boolean event(View v, MotionEvent e)
{
//do something
}
});
Create a class variable for your statement, than you can attach an OnChangeListener to your statement in the onCreate method of your Activity
public class DummyActivity extends Activity {
interface OnStateChangeListener{
public void onAttach(Activity activity);
public void onStateChange(boolean state);
}
private boolean state;
private OnStateChangeListener listener;
private YourClass stateChangedCallback;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listener = new OnStateChangeListener() {
private Activity currentActivity;
#Override
public void onAttach(Activity activity) {
currentActivity = activity;
}
#Override
public void onStateChange(boolean state) {
if (((DummyActivity) this.currentActivity).state != state) {
stateChangedCallback.doSomething();
((DummyActivity) this.currentActivity).state = state;
}
}
};
}
private void yourFunction() {
boolean state = true;
listener.onStateChange(state);
}
}
I'm making a conversion from 2 clases that extends ListActivity to extend ListFragment due to code proposals.
As I know, the Fragment is related to the Activity, so at first using getActivity()... should do the work to adapt most methods. Other times, I've defined Activity activity_context; and I use this.
Anyway, I have some functions that I'm not able to adapt, and I would need some help.
The first is: RecentCallsListActivity extends Fragment
public class RecentCallsListActivity extends ListFragment
...
private static final class QueryHandler extends AsyncQueryHandler {
private final WeakReference<RecentCallsListActivity> mActivity;
...
public QueryHandler(Context context) {
super(context.getContentResolver());
mActivity = new WeakReference<RecentCallsListActivity>(
(RecentCallsListActivity) context); //GETTING THE ERROR HERE
}
ERROR: Cannot cast from context to RecenCallsListActivity
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
mQueryHandler = new QueryHandler(activity_context);
The second is: CallDetailActivity extends Fragment
public class CallDetailActivity extends ListFragment
...
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_CALL: {
// Make sure phone isn't already busy before starting direct call
TelephonyManager tm = (TelephonyManager)
getActivity().getSystemService(Context.TELEPHONY_SERVICE);
if (tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
Intent callIntent = new Intent(Intent.ACTION_CALL,
Uri.fromParts("tel", mNumber, null));
startActivity(callIntent);
return true;
}
}
}
return super.onKeyDown(keyCode, event); //GETTING IT HERE
}
ERROR: The method onkeyDown(int, keyevent) is undefined for the type ListFragment
QueryHandler
At runtime you're providing a class that is not a RecentCallsListActivity or extend from it. The way of handling this is to define an interface that exposes an API that should be implemented by parent activity. If you have multiple activities that need to implement this interface and have the same implementation, you can make a super Activity that implements your interface and each of your activity will extend from this super class. But if you have a single class don't need to do this.
onKeyDown handling - as you can see from API, the fragment doesn't expose any onKeyDown. I have some ideas why this was not implemented, but you can delegate this action from activity to fragment, so that if the fragment is not present and it doesn't wish to consume the event, then you can call activity's super.onKeyDown.
Maybe some code will provide some light and will be helpful.
Sample fragment class:
public class QueryFragment extends Fragment {
public static interface RecentCallsLister {
public void someAction();
}
private RecentCallsLister recentCallsListener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof RecentCallsLister) {
this.recentCallsListener = (RecentCallsLister) activity;
} else {
throw new ClassCastException("Parent class does not implement RecentCallsLister");
}
}
#Override
public void onDetach() {
this.recentCallsListener = null;
super.onDetach();
}
public boolean manageOnKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_CALL) {
// your specific code
return true;
}
return false;
}
}
Sample parent activity class:
public class QueryParentActivity extends FragmentActivity implements RecentCallsLister {
private static final String QUERY_FRAGMENT_TAG = "QUERY_FRAGMENT_TAG";
protected void addQueryFragment() {
QueryFragment fragment = new QueryFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.where_do_want_to_have_me, fragment, QUERY_FRAGMENT_TAG).commit();
}
#Override
public void someAction() {
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (queryFragmentConsumedKeyDown(keyCode, event)) {
return true;
}
return super.onKeyDown(keyCode, event);
}
private boolean queryFragmentConsumedKeyDown(int keyCode, KeyEvent event) {
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentByTag(QUERY_FRAGMENT_TAG);
if (fragment != null) {
return ((QueryFragment) fragment).manageOnKeyDown(keyCode, event);
}
return false;
}
}
EDIT for your first issue:
replace the QueryHandler constructor from:
public QueryHandler(Context context) {
super(context.getContentResolver());
mActivity = new WeakReference<RecentCallsListActivity>((RecentCallsListActivity) context);
}
to:
public QueryHandler(Context context, RecentCallsListActivity fragmentInstance) {
super(context.getContentResolver());
mActivity = new WeakReference<RecentCallsListActivity>(fragmentInstance);
}
Instantiate it as: mQueryHandler = new QueryHandler(activity_context, this);
In my Android app, I have a custom View that receives touch events. However, it doesn't react every time I touch it - only sometimes. From what I can tell, if I touch the screen, move my finger, and then let go - even if I move only a little - the event is picked up, but if I tap the screen too quickly for my finger to slide across it, nothing happens. How can I fix this?
Here is the View's code:
public class SpeedShooterGameView extends GameActivity.GameView {
public SpeedShooterGameView(Context arg0, AttributeSet arg1) {
super(arg0, arg1);
}
#Override
protected GameThread getNewThread(SurfaceHolder holder, Context context) {
return new SpeedShooterGameThread(holder, context);
}
// Program is driven by screen touches
public boolean onTouchEvent(MotionEvent event) {
SpeedShooterGameThread thread = (SpeedShooterGameThread) getThread();
if (thread.isRunning()) {
return thread.recieveTouch(event);
} else {
return false;
}
}
}
I am pretty confident that the object returned in the line SpeedShooterGameThread thread = (SpeedShooterGameThread) getThread(); is working as I expect it to, but if the code above looks fine, I'll post the relevant code from that class as well. When thread.recieveTouch(event); is called, the MotionEvent is being sent to another thread.
EDIT: I'll go ahead and post the code for SpeedShooterGameThread:
public class SpeedShooterGameThread extends GameActivity.GameView.GameThread {
//... snip ...
private Queue<MotionEvent> touchEventQueue;
//... snip ...
public synchronized final void newGame() { //called from the constructor, used to go to a known stable state
//... snip ...
touchEventQueue = new LinkedList<MotionEvent>();
//... snip ...
}
//...snip...
public synchronized boolean recieveTouch(MotionEvent event) {
return touchEventQueue.offer(event);
}
private synchronized void processTouchEvents() {
synchronized (touchEventQueue) {
while (!touchEventQueue.isEmpty()) {
MotionEvent event = touchEventQueue.poll();
if (event == null) {
continue;
}
//... snip ....
}
}
}
//... snip ...
}
I fixed the bug by taking the Queue<MotionEvent> out entirely. My code now looks something like this:
The thread no longer uses a Queue, and MotionEvents are immediately processed when recieveTouch() is called:
public class SpeedShooterGameThread extends GameActivity.GameView.GameThread {
//The touchEvent member has been removed.
//... snip ...
public synchronized final void newGame() { //called from the constructor, used to go to a known stable state
// touchEvents is no longer initialized.
//...snip...
}
//...snip...
public synchronized boolean recieveTouch(MotionEvent event) {
//Immediately handle the MotionEvent here,
//or return false if the event isn't processed
}
// The processTouchEvents() method is removed.
//... snip ...
}
The view is unchanged:
public class SpeedShooterGameView extends GameActivity.GameView {
public SpeedShooterGameView(Context arg0, AttributeSet arg1) {
super(arg0, arg1);
}
#Override
protected GameThread getNewThread(SurfaceHolder holder, Context context) {
return new SpeedShooterGameThread(holder, context);
}
// Program is driven by screen touches
public boolean onTouchEvent(MotionEvent event) {
SpeedShooterGameThread thread = (SpeedShooterGameThread) getThread();
if (thread.isRunning()) {
return thread.recieveTouch(event);
} else {
return false;
}
}
}
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
}
}