I worked on android swipe gestures, i applied swipe gesture on Item inside recycler view and recycler view is inside fragment and then fragment is inside activity and activity has Scroll View, The problem was that parent activity was intercepting swipe gesture of item inside child recycler view, After so much hard i found solution and this code is working fine, Is there any other better solution to this problem? if yes then post it in answer otherwise vote my code so that i can use it in my app without any fear of failure. Here is my code
listingView.setOnTouchListener(new OnSwipeTouchListener(myContext)
{
public void onClick() {
ViewListing();
}
// #Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_UP:
LockableScrollView.setScrollingEnabled(true);
break;
case MotionEvent.ACTION_MOVE:
x2 = event.getX();
float deltaX = x2 - x1;
if (Math.abs(deltaX) > 20)
{
LockableScrollView.setScrollingEnabled(false);
// Left to Right swipe action
if (x2 > x1)
{
hideSidePanel();
LockableScrollView.setScrollingEnabled(true);
}
else
//MH: Right to Left Swipe
{
showSidePanel();
LockableScrollView.setScrollingEnabled(true);
}
}
break;
}
return super.onTouch(v, event);
}
});
Lockable Scroll View code
public class LockableScrollView extends ScrollView {
#Override
public void setClipChildren(boolean clipChildren) {
clipChildren=false;
super.setClipChildren(clipChildren);
}
#Override
public void setFocusable(int focusable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusable=View.NOT_FOCUSABLE;
}
super.setFocusable(focusable);
}
// true if we can scroll (not locked)
// false if we cannot scroll (locked)
private static boolean mScrollable = true;
public LockableScrollView(Context context) {
super(context);
}
public LockableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LockableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public LockableScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public static void setScrollingEnabled(boolean enabled) {
mScrollable = enabled;
}
public static boolean isScrollable() {
return mScrollable;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//MH: if we can scroll pass the event to the superclass
if (mScrollable) return super.onTouchEvent(ev);
//MH: only continue to handle the touch event if scrolling enabled
return mScrollable; // mScrollable is always false at this point
default:
return super.onTouchEvent(ev);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//MH: Don't do anything with intercepted touch events if
//MH: we are not scrollable
if (!mScrollable) return false;
else return super.onInterceptTouchEvent(ev);
}
}
Related
I have a vertically scrolling RecyclerView and I want to allow items on it to be clicked and reordered.
Each item in the RecyclerView has a "StatusIndicator" on the left hand side (inheriting from an AppCompatImageButton). I've added a GestureDetector to the StatusIndicator and this seems to work except that the vertical drag doesn't get sent to the StatusIndicator.
The behaviour I see is that if I attempt vertical scrolling on the StatusIndicator (intending to trigger a drag) neither the drag is triggered nor the RecyclerView is scrolled. If I continue the scrolling gesture but scroll horizontally suddenly I can drag the item.
I'm assuming the ACTION_DOWN event reaches the StatusIndicator's GestureDetector to trigger the drag but that the RecyclerView captures the following (predominantly vertical) ACTION_MOVE assuming it needs to handle them for it's vertical scroll. Once the gesture involves more horizontal than vertical movement RecyclerView::onInterceptTouchEvent no longer captures the event (confirmed in debug) and the ACTION_MOVEs are then sent to the StatusIndicator as intended.
What is the correct approach here?
The relevant bits of StatusIndicator.java:
class StatusIndicator extends AppCompatImageButton {
class GestureTouch extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent event) {
return true;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (mStatusIndicatorListener != null)
mStatusIndicatorListener.incrementStatus();
createDrawableState();
return true;
}
#Override
public boolean onScroll (MotionEvent e1,
MotionEvent e2,
float distanceX,
float distanceY) {
mStatusIndicatorListener.startDrag();
return true;
}
}
private StatusIndicatorListener mStatusIndicatorListener;
private GestureDetector mTouchDetector;
public StatusIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchDetector = new GestureDetector(context,new GestureTouch());
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mTouchDetector.onTouchEvent(event);
return true;
}
I believe what was happenning was that any ACTION_MOVE event was heading down the stack and being captured by the RecyclerView within its onInterceptTouchEvent method. Within this RecyclerView.LayoutManager.canScrollVertically() is used to determine that the RecyclerView should capture the event.
In the end I inherited from LinearLayoutManager and overrode canScrollVertically() so I could manually block it with a call to a new method. Then, within RecyclerView.onInterceptTouchEvent, I checked whether the incoming ACTION_DOWN event was over the StatusIndicator and, if so, forced RecyclerView.LayoutManater.canScrollVertically() to return false until the next ACTION_DOWN event.
This is a pretty hacky solution but appears robust. If anyone comes along with a "proper" solution I'd be keen to see it!
The new layout manager:
public class ListLayoutManager extends LinearLayoutManager {
boolean mScrollBlocked;
public ListLayoutManager(Context context) {
super(context);
mScrollBlocked = false;
}
#Override
public boolean canScrollVertically() {
return (!mScrollBlocked && super.canScrollVertically());
}
public void blockScrolling() {
mScrollBlocked = true;
}
public void unblockScrolling() {
mScrollBlocked = false;
}
}
The extended RecyclerView with overridden onInterceptTouchEvent:
ListLayoutManager mListLayoutManager;
public ListRecyclerView(Context context) {
super(context);
}
public ListRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ListRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// We override touch handling so the RecyclerView doesn't handle
// anything which occurs over the StatusIndicator.
#Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
mListLayoutManager.unblockScrolling();
View child = findChildViewUnder(e.getX(), e.getY());
if (child != null) {
StatusIndicator statusInd = child.findViewById(R.id.rowStatus);
if ((statusInd != null) &&
(e.getX() <= statusInd.getWidth())) {
mListLayoutManager.blockScrolling();
}
}
}
return super.onInterceptTouchEvent(e);
}
public void setListLayoutManager(ListLayoutManager lm) {
mListLayoutManager = lm;
setLayoutManager(mListLayoutManager);
}
}
I'm new to Java and android development and I want to create this basic app, which consists of multiple CustomViews, where I could move different shapes drawn on canvas.
My idea is to have one class that handles MotionEvents and passes them to individual CustomViews.
My CustomView class:
public class CustomView extends View implements MoveDetector.OnMoveListener {
private MoveDetector mMoveDetector;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init(#Nullable AttributeSet set){
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#626696"));
}
#Override
public boolean onTouchEvent(MotionEvent event){
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
mMoveDetector = new MoveDetector(this);
mMoveDetector.onTouchEvent(event);
float cx = mMoveDetector.getCx();
float cy = mMoveDetector.getCy();
Toast.makeText(getContext(),"why u packin heat bro?"+Float.toString(cx)+" "+Float.toString(cy),Toast.LENGTH_SHORT).show();
Log.d("Moveee", "Move: " + Float.toString(cx)+" " + Float.toString(cy));
}
return super.onTouchEvent(event);
}
#Override
public void OnMove(MoveDetector moveDetector) {
}
}
My MoveDetector Class:
public class MoveDetector {
private float cx,cy, mCislo1,mCislo2;
public float getCx() {
return mCislo1;
}
public float getCy() {
return mCislo2;
}
private OnMoveListener mListener;
public MoveDetector(OnMoveListener listener){
mListener = listener;
}
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
float x = event.getX();
float y = event.getY();
mCislo1 = cXX(x);
mCislo2 = cYY(y);
if (mListener != null) {
mListener.OnMove(this);
}
break;
}
return true;}
public static interface OnMoveListener {
public void OnMove(MoveDetector moveDetector);
}
public float cXX(float x){
cx = x;
return cx;
}
public float cYY(float y){
cy = y;
return cy;
}
}
It doesn't seem to be working and I can't point why.
I searched a solution for ages and couldn't find any.
So I'd be really thankful if someone could help me out.
P.S.: Sorry, if my code is messy: I'm still learning.
I have created on basic View which has Red color in background.I am using that view in my activity.I have setOnTouch listner to same view.
on touch events i am moving that view.
Before touch event it looks like this
After MotionEvent.ACTION_MOVE it look like this
After motion event i want view to be redrawn.I don't want white portion to be shown.
it should redraw it like google map does on moving map in any direction.
following code is of my basic custom view
public class CustomVIew extends View {
public CustomVIew(Context context) {
super(context);
}
public CustomVIew(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomVIew(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.RED);
}
}
This is my OnTouch method implementation
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
_xDelta = v.getX() - event.getRawX();
_yDelta = v.getY() - event.getRawY();
Log.d(TAG, "onTouch: X:" + _xDelta + " Y:" + _yDelta);
break;
case MotionEvent.ACTION_MOVE:
v.animate()
.x(event.getRawX() + _xDelta)
.y(event.getRawY() + _yDelta)
.setDuration(10)
.start();
break;
default:
return false;
}
return true;
}
Thanx in advance
you may need to invalidate the view .
this helps:
When it's necessary to execute invalidate() on a View?
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
_xDelta = v.getX() - event.getRawX();
_yDelta = v.getY() - event.getRawY();
Log.d(TAG, "onTouch: X:" + _xDelta + " Y:" + _yDelta);
break;
case MotionEvent.ACTION_MOVE:
//do something like change the picture
v.invalidate();
break;
default:
return false;
}
return true;
}
I have a layout like that:
<NestedScrollView>
<RecyclerView> // vertical recycler view
<RecyclerView/> // horizontal recycler view
<RecyclerView/>
<RecyclerView/>
...
<RecyclerView>
</NestedScrollView>
The result looks like Google play store:
And I disabled NestedScrolling in horizontal Recycler view:
horizontalRecyclerView.setHasFixedSize(true);
horizontalRecyclerView.setNestedScrollingEnabled(false);
My problem:
The vertical recyclerview does not scroll fling, whenever ACTION_UP happen, the vertical recyclerview also stop scrolling.
How can I nest vertical recyclerview inside nestedscrollview, and horizontal recyclerview inside vertical recyclerview like Playstore and keep the scroll smooth.
Solved:
Using custom nested scroll view of #vrund purohit (code below), and disabled nestedscroll both vertical and horizontal recyclerview:
verticalRecyclerView.setNestedScrollingEnabled(false);
... add each horizontal recyclerviews:
horizontalRecyclerView.setNestedScrollingEnabled(false);
Use below code for smooth scroll:
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
Add this in your RecyclerView xml:
android:nestedScrollingEnabled="false"
I had this same problem and I solved this issue by customizing NeatedScrollView.
Here is the class for that.
MyNestedScrollView
public class MyNestedScrollView extends NestedScrollView {
#SuppressWarnings("unused")
private int slop;
#SuppressWarnings("unused")
private float mInitialMotionX;
#SuppressWarnings("unused")
private float mInitialMotionY;
public MyNestedScrollView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
ViewConfiguration config = ViewConfiguration.get(context);
slop = config.getScaledEdgeSlop();
}
public MyNestedScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MyNestedScrollView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private float xDistance, yDistance, lastX, lastY;
#SuppressWarnings("unused")
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
xDistance = yDistance = 0f;
lastX = ev.getX();
lastY = ev.getY();
// This is very important line that fixes
computeScroll();
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
xDistance += Math.abs(curX - lastX);
yDistance += Math.abs(curY - lastY);
lastX = curX;
lastY = curY;
if (xDistance > yDistance) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
public interface OnScrollChangedListener {
void onScrollChanged(NestedScrollView who, int l, int t, int oldl,
int oldt);
}
private OnScrollChangedListener mOnScrollChangedListener;
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollChangedListener != null) {
mOnScrollChangedListener.onScrollChanged(this, l, t, oldl, oldt);
}
}
}
Happy coding.
[RESOLVED] I have same issue with Horizontal recycleview. Change Gradle repo for recycleview
compile 'com.android.support:recyclerview-v7:23.2.1'
Write this: linearLayoutManager.setAutoMeasureEnabled(true);
Fixed bugs related to various measure-spec methods in update
Check http://developer.android.com/intl/es/tools/support-library/features.html#v7-recyclerview
I have found issue with 23.2.1 library: When item is match_parent recycle view fill full item to view, please always go with min height or "wrap_content".
Thanks
I've solved the issue by using below code:
myRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false){
#Override
public boolean canScrollHorizontally() {
return true;
}
#Override
public boolean canScrollVertically() {
return true;
}
});
I have a custom ImageButton and I want to change it's image on click. So far I havent been able to get the onClick method inside the button to perform it's action.
public class FlashButtonView extends ImageButton{
private Drawable mFlashOffSrc, mFlashOnSrc, mFlashAutoSrc;
private Drawable mCurrentFlashMode = mFlashAutoSrc;
public FlashButtonView(Context context) {
super(context);
}
public FlashButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlashButtonView, 0, 0));
}
public FlashButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlashButtonView, 0, defStyleAttr));
}
private void init(TypedArray typedArray) {
try {
mFlashAutoSrc = typedArray.getDrawable(R.styleable.FlashButtonView_autoSrcImage);
mFlashOnSrc = typedArray.getDrawable(R.styleable.FlashButtonView_onSrcImage);
mFlashOffSrc = typedArray.getDrawable(R.styleable.FlashButtonView_offSrcImage);
} finally {
typedArray.recycle();
}
setImageDrawable(mFlashAutoSrc);
mCurrentFlashMode = mFlashAutoSrc;
setOnClickListener( new OnClickListener() {
#Override
public void onClick(View v) {
if(mCurrentFlashMode == mFlashAutoSrc) {
setImageDrawable(mFlashOnSrc);
mCurrentFlashMode = mFlashOnSrc;
}
else if(mCurrentFlashMode == mFlashOnSrc){
setImageDrawable(mFlashOffSrc);
mCurrentFlashMode = mFlashOffSrc;
}
else{
setImageDrawable(mFlashAutoSrc);
mCurrentFlashMode = mFlashAutoSrc;
}
}
});
}
}
And this is how my XML looks like:
<!--.___ Flash on/off switcher __.-->
<blablabla.FlashButtonView
custom:autoSrcImage="#drawable/button_autoflash"
custom:offSrcImage="#drawable/button_noflash"
custom:onSrcImage="#drawable/button_flash"
android:id="#+id/flash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="#dimen/distance_20dp"
android:layout_marginStart="#dimen/distance_20dp"
android:layout_marginBottom="#dimen/distance_20dp"
android:background="#android:color/transparent"
android:clickable="true"/>
What am I missing or doing wrong?
Implement onTouchEvent() method. Something like this:
float touched_x, touched_y;
boolean touched = false;
#Override public boolean onTouchEvent(MotionEvent event) {
touchCounter++;
touched_x = event.getX();
touched_y = event.getY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
touched = true;
break;
case MotionEvent.ACTION_MOVE:
touched = true;
break;
case MotionEvent.ACTION_UP:
touched = false;
break;
case MotionEvent.ACTION_CANCEL:
touched = false;
break;
case MotionEvent.ACTION_OUTSIDE:
touched = false;
break; default:
}
return true;
}
A minimal example with:
public class CustomButton extends ImageButton {
public CustomButton(Context context) {
super(context);
init();
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
this.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d(getClass().getSimpleName(), "ButtonPressed");
}
});
}
}
and they layout:
<com.example.bossb.test.CustomButton
android:layout_width="match_parent"
android:layout_height="match_parent" />
works fine for me.