My ultimate goal is to stop the scroll in specific locations, so the objects being viewed are shown completely, and the solution I came up with is by checking the scrollX location of the view when the touch is released. I did have a momentum issue, as the scroll could still continue due to the scroll velocity also after catching the touch event, I solved that using MotionEvent.ACTION_CANCEL, which I am not happy with, as the scrollTo is optically pleasing, but the MotionEvent.ACTION_CANCEL kills its effect.
My question is, is there a more elegant way to do this? Ideal would be if I could simply instruct my ScrollView NOT to have momentum/velocity.
myView.setOnTouchListener(new HorizontalScrollView.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
event.setAction(MotionEvent.ACTION_CANCEL);
int x = myView.getScrollX();
if (x < 150)
myView.scrollTo(0,0);
else if (x<450)
myView.scrollTo(300,0);
else
myView.scrollTo(600,0);
}
return false;
}
});
If you want to scroll precisely to show specific view, this answer should help you. What you have to do is to call myView.scrollTo(0, view.getBottom()) or myView.smoothScrollTo(0, view.getBottom()) on UI thread. This way, view object will be displayed fully.
EDIT: It seems to me that what you're trying to achieve here is very close to what ViewPager does: your intention is to move between the views with a swipe/fling gesture, providing full visibility to the paged views contents.
If you were to use ViewPager, this is how your code may have looked:
viewPager.setAdapter(new PagerAdapter() {
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public int getCount() {
return getViewCount();
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
View view = getViewAtPosition(position);
container.addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object view) {
container.removeView((View) view);
}
});
Where getViewCount() and getViewAtPosition() methods have to be implemented accordingly to how many displayed views you want and the way they are instantiated.
You should use an onScrollListener if you want to get the current position when the scrollView is scrolled.
scrollView.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int scrollX = rootScrollView.getScrollX(); //for horizontalScrollView
int scrollY = rootScrollView.getScrollY(); //for verticalScrollView
//DO SOMETHING WITH THE SCROLL COORDINATES
}
});
Related
I'm trying to develop a simple but custom drag and drop feature of recyclerview items. The idea is to drag a child to a specific view, represented by a FrameLayout, that is not a child of recyclerview.
When a child is dropped onto it it will be deleted otherwise it will simply return to its original position.
I can easily achieve this behaviour without any animation effect, but my purpose is to have smooth animation on the UP dragevent and on the restoration of the element. Now whenever a child is dragged, I create a DragShadow of it and I delete the child in the dataset and notify the removal with adapter.notifyItemRemoved(position).
The last one allows me to have simple remove animation provided by Recyclerview itself.
The Code will explain the mechanism better than words:
public boolean dragNdrop(View arg1, int position) {
// ------------------------
final RecyclerAdapter adapter= ListItemFragment.getAdapter();
/*Keep track of the position and the object for restoring purpose*/
ListItemFragment.indexAbruptedRemoved=position;
ListItemFragment.itemAbruptedRemoved=adapter.getItem(position);
ClipData clipData = ClipData.newPlainText("", "");
View.DragShadowBuilder dsb = new View.DragShadowBuilder(arg1);
arg1.startDrag(clipData, dsb, arg1, 0);
adapter.remove(position);
adapter.notifyItemRemoved(position);
//--------------------
}
getter method for the adapter:
protected static RecyclerAdapter getAdapter() {
return (RecyclerAdapter) recList.getAdapter();
}
dragNdrop is called by a drag listener in the custom adapter:
public void onBindViewHolder(MyHolder myHolder, int position) {
//----------------------------------
myHolder.rowCard.setLongClickable(true);
myHolder.rowCard.setOnClickListener(new MyClickListener.ContainerListener(context,
myHolder.getAdapterPosition(), rowObject, myHolder.imagePreView));
myHolder.rowCard.setOnLongClickListener(new MyClickListener.DragListener(context,
myHolder.getAdapterPosition(), myHolder.rowCard));
//----------------------------------
}
LongCLickListener:
static class DragListener implements View.OnLongClickListener {
private Context context;
private int position;
private View dragged;
DragListener(Context context, int position, View dragged) {
this.context = context;
this.position = position;
this.dragged = dragged;
}
#Override
public boolean onLongClick(View v) {
return ((MainActivity) context).dragNdrop(dragged, position);
}
}
The FrameLayout called trashPanel has the following code:
trashPanel.setOnDragListener(new View.OnDragListener() {
#Override
public boolean onDrag(View view, DragEvent dragEvent) {
int dragAction = dragEvent.getAction();
View dragView = (View) dragEvent.getLocalState();//this
//is properly the object we 've passed with startdrag()
switch (dragAction) {
case DragEvent.ACTION_DRAG_EXITED:
imageView.setImageDrawable(getResources().getDrawable(R.drawable.bowl));
break;
case DragEvent.ACTION_DRAG_ENTERED:
imageView.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.waste));
break;
case DragEvent.ACTION_DRAG_ENDED:
ended(view, dragEvent);
break;
case DragEvent.ACTION_DROP:
Log.i(TAG,"HALOOOOO DROPPED");
drop();
break;
}
return true;
}//[m] end on drag
private void drop() {
String itemToRemove = itemAbruptedRemoved.getSessionName();
Toast.makeText(getActivity(), "DELETED: " + itemToRemove+
" list length:"+items.size()+" adapter length:"+mAdapter.size(),
Toast.LENGTH_SHORT).show();
/**
* HAPTIC FEEDBACK
*/
Vibrator v = (Vibrator) getActivity().getSystemService(Service.VIBRATOR_SERVICE);
long pattern[] = {25, 25, 50};
v.vibrate(pattern, -1);// -1 to not repeat
}
private void ended(View view, DragEvent dragEvent) {
if ( dropEventNotHandled(dragEvent) ) {
mAdapter.insert(itemAbruptedRemoved, indexAbruptedRemoved);
mAdapter.notifyItemInserted(indexAbruptedRemoved);
//********---------->mAdapter.notifyDataSetChanged();
Log.i(TAG,"AGGIUNTO");
}
//change the image inside trash_panel
imageView.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.bowl));
//due to implementations detail it must be done in this way(otherwise mutithread exception is throwned)
view.post(new Runnable() {
public void run() {
/**
* Set animation for fabNew Button and trash_panel
* in order to make them smoothly disappear
* and place them in their original positions
*
*/
------------------------------------
}
});
}
private boolean dropEventNotHandled(DragEvent dragEvent) {
return !dragEvent.getResult();
}
});
The problem is that sometimes when I start to drag an item, I obtain the correct DragShadow, but the item removed is wrong.
In the code i higlited with ***----> the call to notifyDataSetChanged(), because if i call it, things work properly, but without animation.
I know there are utility classes such as ItemTouchHelper.Callback that should be used to manipulate the movement and animation of recyclerlist children, but I can't figure out how to manage to make them do what I want to do. I saw a couple of methods that could be used to achieve this, such as onChildDrawOver and onChildDraw, but i still don't know how to use them in order to intercept dragevent.DROP . I also know the existence of the interface for the LLM called ItemTouchHelper.ViewDropHandler that has the abstract method prepareForDrop, but I still don't know how to use it in a proper way.
Thanks in advance to everyone who will help me!
Take a look at ItemTouchHelper. It will allow you customize your drag drawing. See Support7Demos for an example implementation.
I have a layout which requires me to put a grid view inside a scroll view, i have read this is not suggested but my layout requires this.
When inserting a GridView in a ScrollView the grid does not scroll! I have got around this with the following.
The problem i have is that i have is the grid view will not scroll smoothly, if i hold my finger down and drag it scrolls, but if i do a swipe type of gesture it does not do a smooth scroll as expected. as soon as i remove my finger the scrolling stops on the grid view.
gridView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN
|| event.getAction() == MotionEvent.ACTION_MOVE) {
gridView.requestDisallowInterceptTouchEvent(true);
}
return false;
}
});
i tried adding this but no luck
gridView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view,
int scrollState) { // TODO Auto-generated method
// stub
}
#Override
public void onScroll(AbsListView view,
int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
// TODO Auto-generated method stub
gridView.requestDisallowInterceptTouchEvent(true);
}
});
EDIT:
Just to give a bit more information
the grid view is inserted programatically, inside a relative layout which sits in a linear layout inside a scrollview
I would Suggest you to use ExpandableHeightGridView. It works for me very fine.Hope this will help you :)
To Answer my own question, i found a solution taking some parts from this example, works perfectly
https://github.com/Durgadass/ScrollInsideScroll
I have a ListView inside ScrollView. I can enable scroll of ListView by
listView.getParent().requestDisallowInterCeptTouchEvent(true);
But the problem is when i scroll up in listView and it reaches top it should scroll to parent view i.e. parent scroll has to work . How can i do this ? any suggestion please.
listView.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
return true; // Indicates that this has been handled by you and will not be forwarded further.
}
return false;
}
});
OR
To make the View unselectable just get the view and .setClickable(false)
OR
listView.setScrollContainer(false);
You can override ScrollView class and insert these methods inside:
private boolean isScrollEnabled = true;
public void enableScroll(boolean isScrollEnabled ) {
this.isScrollEnabled = isScrollEnabled ;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isScrollEnabled) {
return super.onInterceptTouchEvent(ev);
} else {
return false;
}
}
This is the cleanest solution to achieve this. You only call scrollView.enableScroll(true) to enable scrolling or scrollView.enableScroll(false) to disable it.
I would suggest to embed your upper view i.e any viewgroup above list view into listview header. ListView has a method, listview.addHeaderView(). That way you would be able to scroll your list (Whole View) even on small size display and you don't need scrollview.
I'm having a hard time trying to figure this out. I have a gridview of 8 buttons. At the moment I'm using an onItemClickListener to trigger the buttons actions, however this produces two problems for me.
1) The buttons action happens after the button has been unpressed.
2) Two buttons cannot the pressed at the same time, you must release the first button.
As I have learnt, an onTouchListener should resolve my first issue, though I'm not sure how to determine which button has been pressed. My code for the onItemClickListener is as follows
gridview.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
Toast.makeText(Activity.this, "" + position, Toast.LENGTH_SHORT).show();
}
});
Now with the above, I know exactly which button has been pushed. I believe the code for implementing as an onTouchListener is as follows
gridview.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return false;
}
}) {
How am I supposed to determine which button has been pressed using MotionEvent? Before I was getting passed 'position' and it made this fairly easy. I also need to account for if two or more buttons have been pushed simultaneously/without letting another one go.
Does anyone know how to achieve this?
Having hit this very issue recently and coming across this post in my quest for help, I wanted to add two things from what I did which seem to have worked:
1) I added the onTouchListener to the object in the adapter rather than the activity or gridview.
2) In the OnTouchListener, I looked for MotionEvent.ACTION_DOWN (first finger touch) and MotionEvent.ACTION_POINTER_DOWN (subsequent finger touches), this way I can get multitouches and process them immediately without waiting for the user to lift their finger(s).
Note that I called it ImageAdapter, even though I've added a TextView to each as that way I can use the TextView background for the image, but add invisible text to the TextView so it works with Talkback):
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return numCols * numRows;
}
public Object getItem(int position) {
return this;
}
public long getItemId(int position) {
return position;
}
// create a new TextView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView;
if (convertView == null) { // if it's not recycled, initialize some attributes
textView = new TextView(mContext);
} else {
textView = (TextView) convertView;
}
// place any other initial setup needed for the TextView here
// here's our onTouchListener
textView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
boolean returnValue;
int thePosition = v.getId();
// MotionEvent.ACTION_DOWN gets the first touch
// MotionEvent.ACTION_POINTER_DOWN gets any subsequent touches (if you place a second finger on the screen)
// Between these I can get touches as soon as they happen, including multitouch support, without needing to wait until the user lifts their finger.
if ((event.getAction() == MotionEvent.ACTION_DOWN) || (event.getAction() == MotionEvent.ACTION_POINTER_DOWN)) {
TextView textView;
if (v == null) { // if it's not recycled, initialize some attributes
textView = new TextView(mContext);
} else {
textView = (TextView) v;
}
// Do any processing based on the touch - I call a function and pass the position (number of cell, 1..n) and textview so can make changes to it as needed
ScreenTapped(thePosition, textView);
// I used a returnValue
returnValue = true;
} else returnValue = false;
return returnValue;
});
return textView;
} // getView
} //imageadapter
I am infact trying to figure the same thing out. I got as far as figuring out which gridcell has been clicked using the following code
public boolean onTouch(View v, MotionEvent me) {
float currentXPosition = me.getX();
float currentYPosition = me.getY();
int position = gridView.pointToPosition((int) currentXPosition, (int) currentYPosition);
Position gives you the number on the gridView, and you can supposedly retrieve that particular item as following
gridView.getItemAtPosition(position)
But that is where I am stuck. My gridView has Textview items in it, and I am having trouble converting the item to a textview and then performing operations on it.
Hope this helps!
When using gridView the philosophy is:
the grid view implements the onTouchLister
when touch happens onTouchLister gathers the coordinates (a lot :) )
for all ACTION_MOVE events
when the touch event is MOVE_UP, calculate the real positions under
the coordinates and return the item in the grid
So the solution would be:
In your activity where you have findViewById(some_grid_view)
//Register handler for the onTouch event of gridView in your activity
gridView.setOnTouchListener(new MyActivityOnTouchListener(this));
NOTE: my onTouch listener is implemented in another class (MyActivityOnTouchListener) instead of inside the activity
...then in the MyActivityOnTouchListener class you implement the onTouch method:
public class CalendarActivityOnTouchListener implements View.OnTouchListener {
private MyActivity myActivityContext;
private GridView mGridView;
private HashSet<Point> movementCoordinates = new HashSet<Point>;
//Constructor
public MyActivityOnTouchListener (MyActivity context){
this.myActivityContext= context;
mGridView= myActivityContext.getGridView(); //assign touched gridView into a local variable
}
#Override
public boolean onTouch(View v, MotionEvent event) {
/*
* NOTE:
* ACTION_MOVE fires events until you release it
* ACTION_UP once you release it fires it
*/
//while touching the grid a bunch of ACTION_MOVE events are dispatched
if (event.getAction() == MotionEvent.ACTION_MOVE) {
//gather all coordinates touched (in a set to avoid duplicates)
movementCoordinates.add(new Point((int)event.getX(), (int)event.getY()));
return true;
}
//Finally the finger is lifted
if (event.getAction() == MotionEvent.ACTION_UP) {
//convert all movementCoordinates gathered in the previous block into real grid positions
int position;
for(Point p : movementCoordinates){
Log.d("Luka", p.x +" / "+p.y);
position = calendarGridView.pointToPosition(p.x, p.y);
//...Do whatever with the position
}
}
}
}
Be careful about the pointToPosition() method because in some cases it can return -1 instead of the position behind the coordinates. For example, if you have a margin between items in the grid those coordinates cannot return a position, hence the -1
hope it helps...
What's the best way to disable the touch events for all the views?
Here is a function for disabling all child views of some view group:
/**
* Enables/Disables all child views in a view group.
*
* #param viewGroup the view group
* #param enabled <code>true</code> to enable, <code>false</code> to disable
* the views.
*/
public static void enableDisableViewGroup(ViewGroup viewGroup, boolean enabled) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
view.setEnabled(enabled);
if (view instanceof ViewGroup) {
enableDisableViewGroup((ViewGroup) view, enabled);
}
}
}
Override the dispatchTouchEvent method of the activity and like this:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
return true;//consume
}
If you return true all touch events are disabled.
Return false to let them work normally
You could try:
your_view.setEnabled(false);
Which should disable the touch events.
alternatively you can try (thanks to Ercan):
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
return true;//consume
}
or
public boolean dispatchTouchEvent(MotionEvent ev) {
if(!onInterceptTouchEvent()){
for(View child : children){
if(child.dispatchTouchEvent(ev))
return true;
}
}
return super.dispatchTouchEvent(ev);
}
This piece of code will basically propagate this event to the parent view, allowing the touch event, if and only if the inProgress variable is set to false.
private boolean inProgress = false;
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!inProgress)
return super.dispatchTouchEvent(ev);
return true;
}
Use this. returning true will indicate that the listener has consumed the event and android doesn't need to do anything.
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
The easiest way to do this is
private fun setInteractionDisabled(disabled : Boolean) {
if (disabled) {
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
}
}
What about covering a transparent view over all of your views and capturing all touch event?
getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
In Kotlin:
fun View.setEnabledRecursively(enabled: Boolean) {
isEnabled = enabled
if (this is ViewGroup)
(0 until childCount).map(::getChildAt).forEach { it.setEnabledRecursively(enabled) }
}
// usage
import setEnabledRecursively
myView.setEnabledRecursively(false)
I made this method, which works perfect for me. It disables all touch events for selected view.
public static void disableView(View v) {
v.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
if (v instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) v;
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
disableView(child);
}
}
}
It may not be possible for the whole application. You will have to override onTouchEvent() for each view and ignore the user inputs.
Per your comment:
i just want to be able to disable the views of the current activity at some point
you seem to want to disable all touch for the current activity regardless of the view touched.
Returning true from an override of Activity.dispatchTouchEvent(MotionEvent) at the appropriate times will consume the touch and effectively accomplish this. This method is the very first in a chain of touch method calls.
In case you want to disable all the views in a specific layout, one solution is adding a cover ( a front view that fills up the whole layout ) to consume all the touch events, so that no events would be dispatched to other views in that layout.
Specifically, you first need to add a view to the layout in xml file ( note that it should be placed after all the other views ), like
<FrameLayout>
... // other views
<View
android:id="#+id/vCover"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
then, remember to set click listener to that view in your code so that it will consume touch events, like
override fun onCreate(savedInstanceState: Bundle?) {
viewBinding.vCover.setOnClickListener {}
}
That's all you need.
At the point you want to enable all the view, just gone the cover.
This worked for me, I created an empty method and called it doNothing.
public void doNothing(View view)
{
}
Then called this method from onClick event on all the objects I wanted to disable touch event. android:onClick="doNothing"
When the click or touch event is fired nothing is processed.
One more easier way could be disabling it through layout (i.e. .xml) file:
Just add
android:shouldDisableView="True"
for the view you want disable touch events.