My overall goal is to be able of dragging a row from the RecyclerView to another view inside my app.
With a standard RecyclerView with LinearLayoutmanager and no extra mumbo-jumbo my drag-and-drop operations work flawlessly. However, as soon as I introduce a custom library called AndroidSwipeLayout to make each row swipeable to reveal extra actions everything fails and I get the common error:
08-28 09:59:03.465: I/ViewRootImpl(15310): Reporting drop result: false
Also I can see that the only DragEvents that are fired on my receiving view are ACTION_DRAG_STARTED and ACTION_DRAG_ENDED, all other events are skipped. As you can see I am returning true from ACTION_DRAG_STARTED but that doesn't help, my thought is that the custom library somehow eats my event. But I can't pinpoint where.
Here is my OnDragListener:
private class MyDropListener implements View.OnDragListener {
#Override
public boolean onDrag(View v, DragEvent event) {
// Doing some calculations based on event x and y. Not related to the problem.
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
// Some unrelated code, updating how views are displayed
return true;
case DragEvent.ACTION_DRAG_LOCATION:
// Some unrelated code, updating how views are displayed
return true;
case DragEvent.ACTION_DROP:
// Some unrelated code, updating some data and updating how views are displayed
return true;
case DragEvent.ACTION_DRAG_ENDED:
// Some unrelated code, updating how views are displayed
return true;
case DragEvent.ACTION_DRAG_ENTERED:
// Some unrelated code, updating how views are displayed
return true;
case DragEvent.ACTION_DRAG_EXITED:
// Some unrelated code, updating how views are displayed
return true;
default:
return false;
}
}
}
I've experimented now for a few days inside the library but can't find a solid solution exactly where my event dies. Sometimes I've managed to get the drop working, but it's very irregular behaviour.
I've also made a Github issue for this problem:
AndroidSwipeLayout - issue #211
I'm sure this is not specific to this library, but a problem when there's too much gesture detection going on for each view in a list. The library is really excellent I think and I don't wish to write that interaction myself.
Any thoughts, or comments, are welcome and appreciated. Even if you don't know the exact solution.
Thanks.
This was actually not caused by the library, or the listeners, it was caused by an EditText that was located in the same layout.
It was solved by creating a new class that subclasses EditText and ignores the dragEvent.
public class EditTextNoDrag extends EditText {
public EditTextNoDrag(Context context) {
super(context);
}
public EditTextNoDrag(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EditTextNoDrag(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public EditTextNoDrag(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public boolean onDragEvent(DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
return false;
default:
return super.onDragEvent(event);
}
}
}
You can also toggle the focusable of the EditText on and off.
See this related isseu: Prevent drag drop of custom mimetype to EditText
Related
I'm a novice and this is my first Android app. I'm building a card game where the player should be able to tap to move a card (onClick) and also drag a card to a new location (onTouch).
I'm using CardViews for the cards.
I've built the setOnClickListener with no issues. I'm not having the same luck with the onTouchListener. I also really don't know if I should be using onTouch or onTouchListener...or maybe even onDragListener?
Based on YouTube and other StackOverflow posts, I've constructed a onTouchListener like so:
aceSpades.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
// do work
break;
case MotionEvent.ACTION_MOVE:
// do more work
break;
case MotionEvent.ACTION_UP:
// do final work
break;
}
}
}
This works fine, I'm able to move the card around OK within the game. However, it appears that a CardView cannot have both assigned:
setOnClickListener and setOnTouchListener
Also, in the onTouchListener the whole things is highlighted in yellow with the warning that it does not override the performClick.
I've tried to understand in more detail other explanations/solutions, but what I've read so far doesn't make much sense to me.
The most popular solution I have founded instructs the creation of a new package and custom view and let the custom view manage Overrides for onTouch and performClick(). I went as far as creating the file and added it to my xml, but the cardView disappears, so obviously something is wrong. I'll provide the code for that below.
I don't really follow how the custom view solution would work - it seems to need the performClick to be added to the ACTION_UP. For my app, the onClick is a set of different methods, separate from the methods I would include in ACTION_UP. Any assistance would be helpful, I'm really stuck here. Thanks.
Class File:
package views;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
Log.d("Test", "onTouchEvent: Touched!");
switch(event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
performClick();
break;
}
return false;
}
#Override
public boolean performClick() {
super.performClick();
Log.d("Test", "performClick: clicked!");
return true;
}
}
XML:
<views.CustomView
android:id="#+id/test_drag_card"
android:layout_width="100px"
android:layout_height="250px"
android:backgroundTint="#color/standard_red"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
As I shared above, when this code the card is not even visible when I launch the app. I'm testing on a real device. I'm using ConstraintLayout for my XML.
Please let me know if I need to provide any more details for a possible solution. Thanks.
I want to make a view like
Sample image
in which a want to show google maps inside a bottom sheet fragment.
What I've tried
I've tried to show maps inside a bottom sheet dialog fragment but the output isn't what I desire.
What I require is a fixed size view which should be able to display maps. Currently my view is also responding to user gestures to change bottom sheet state but I require gestures to work on map only (e.g for map panning).
When we use the map on BottomSheet, it conflicts touch events. So, need to disallow touch of BottomSheet.
Please find a below custom class which allows the map to move.
public class BottomSheetMapView extends MapView {
public BottomSheetMapView(Context context) {
super(context);
}
public BottomSheetMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BottomSheetMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public BottomSheetMapView(Context context, MapboxMapOptions options) {
super(context, options);
}
#Override
public boolean onInterceptTouchEvent(final MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
getParent().requestDisallowInterceptTouchEvent(false);
break;
default:
break;
}
return super.onInterceptTouchEvent(event);
}
}
I am using Mapbox. So, I use com.example.BottomSheetMapView instead of com.mapbox.mapboxsdk.maps.MapView in xml. Similarly, you can use Google map.
This satisfies your requirement.
I need to implement the same feature. In my case, I used a BottomSheetDialogFragment that contains SupportMapFragment. The problem was, I could only make horizontal gestures on the map like panning it, but not vertical gestures. What needs to be done is to disable the BottomSheet's touch listener while the user is doing some gestures on the map. You can refer to my similar post here to see how it should be done https://stackoverflow.com/a/53740355/1767167
i fought a long time with that problem without finding a solution anywhere.
I have a ListView that contains some RatingBars. Everytime i wanted to scroll and hit accidentially a ratingbar a rating was performed instead of a scroll.
Now I found a simple solution: I wrote my own RatingBar:
public class ScrollableRatingBar extends RatingBar {
public ScrollableRatingBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ScrollableRatingBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrollableRatingBar(Context context) {
super(context);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
return true;
}
case MotionEvent.ACTION_MOVE: {
return true;
}
}
return super.dispatchTouchEvent(ev);
}
}
What happens: if you click at a RatingBar there are 3 different MotionEvents:
-Action_DOWN: Put your finger down
-Action_MOVE: Move your finger
-Action_UP: Put your finger up
You don't want to set the rating at the action "down" or "move". So just return true to signal it has happend. If "up" comes, the programm differes itself if it is a scroll or a rating, depending what you did.
That's all!
I hope someone helps that.
EDIT:
I realize that it is a problem of Android 2.3.7 and lower.
I have a problem using a SeekBar in my code...
I'd like to have a SeekBar which the values are set by me, not by the user. The only solution I've founded and I don't like, is setting enabled to false, but the colors of the seek bar become grey... (so I can't do anything when the user slides the thumb...)
I would get something like that, but without touchable events!
SOLVED:
I solved the problem, as #CommonsWare says:
public class TaskListProgressBar extends SeekBar {
public TaskListProgressBar(Context context) {
super(context);
}
public TaskListProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TaskListProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
#Override
public boolean onTrackballEvent(MotionEvent ev) {
return false;
}
You could subclass it and override onTouchEvent() and onTrackballEvent() (to eat all attempts to silde the thumb) and also use android:focusable="false" (to prevent it from getting the focus, so arrow keys cannot modify the thumb position).
That being said:
I have never tried this, so YMMV.
Please only do this if you are changing the default thumb image, per your screenshot above. Otherwise, users will expect the thumb to be movable, and they will get frustrated when they cannot move it.
I've got a class that extends EditText and overwrites the onTouchEvent()-method in order to see when the corresponding MotionEvents occur:
public class CustomEditText extends EditText {
public CustomEditText(Context context) {
super(context);
}
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomEditText(Context context, AttributeSet attrs, int i) {
super(context, attrs, i);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN: Log.v("ME", "down");
break;
case MotionEvent.ACTION_UP: Log.v("ME", "up");
break;
case MotionEvent.ACTION_MOVE: Log.v("ME", "move");
break;
case MotionEvent.ACTION_CANCEL: Log.v("ME", "cancel");
break;
}
return true;
}
}
When the View isn't inside a ScrollView, everything works as expected: LogCat prints "move" as long as my finger is moving around on the screen.
But when the View is inside a ScrollView and I'm moving my finger vertically, LogCat prints some "move", after a couple of millimeters a "cancel" and then nothing anymore until I replace my finger on the screen. This doesn't happen when I move horizontally.
I think the reason is that at this point the ScrollView recognizes that it should start scrolling now and consequently "steals" the MotionEvents from the View in order to evaluate them itself.
My question is now: How can I prohibit this behaviour without creating a new class extending ScrollView?
Thanking you in anticipation
Daniel R.
Why don't you try to setOnTouchListener
ex:
ScrollView yourScrollView;
yourScrollView.setOnTouchListener(new View.OnTouchListener(){
public boolean onTouch (View v, MotionEvent event){
yourTextView.onTouch(yourTextView,event);
}
});
something in these lines.. please report back so I could edit the answer to the best
(It's me, DanielR. I've now got my own account, sorry for that.)
Thanks a lot for the rapid answer, Sherif. That solves my problem.
What I am actually doing in my app is viewing a scrollable EditText that has 3 areas: a small margin on the left and the right in which you can scroll the View and a main area in the centre in which the common editing actions are performed.
What I am doing to achieve this, is:
1. when the user starts a gesture in the margins, I set the onTouchListener to null, so the ScrollView's scrolling action is performed (read that in a post somewhere around here).
2. touching the main area, the onTouchListener is set to the one you suggested above, so scrolling is disabled AND all touch events reach the EditText. Previously, my onTouchListener was empty so merely scrolling was prohibited. I don't know why I didn't see that myself. I think it's just too late ;)
Once again, thank you a lot.