Android: HorizontalScrollView inside ScrollView - java

I have multiple HorizontalScrollViews inside a ScrollView. Horizontal scroll isn't smooth at all. I have to scroll almost perfectly horizontally for scrolling to work. Is there a simple fix to tweak this ??? Thanks!

You can use Recycler view with Staggered layout manager
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL);
RecyclerViewAdapter recyclerViewAdapter = newRecyclerViewAdapter(this);
recyclerView.setAdapter(recyclerViewAdapter); //Don't miss to initialize your adapter

This class creates a ScrollView containing a HorizontalScrollView combined into one class. You can put stuff inside it using the AddChild() method. The dispatchTouchEvent overide keeps the scrolling smooth so you can pan around with a single slide of the finger.
(I recently used this to wrap a programmatically created TextView)
class MultiScrollView extends ScrollView
{
public HorizontalScrollView hscroll;
public MultiScrollView ( Context context )
{
super( context );
}
public void AddChild( View child )
{
hscroll.addView( child );
}
#Override
public boolean dispatchTouchEvent( MotionEvent event )
{
hscroll.dispatchTouchEvent(event);
onTouchEvent(event);
return true;
}
}

If you are using the horizontal scroll view solution from (http://www.dev-smart.com/archives/34) the solution for the cross focus problem between the scroll view and the list view is blocking the focus to the scroll view once you have focus on the list view.
From a technical point of view you should add the following line to the onScroll function inside the HorizontalListView class.
getParent().requestDisallowInterceptTouchEvent(true);
Hope this helps.

I've found the solution and still can't believe that this is what you have to do to make this work normal! Just added blank onClickListener to the each item in the HorizontalScrollView:
item.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
After this slide is really smooth, both upwards and downwards.

In general, you shouldn't be using nested ScrollViews in Android at all, the behaviour of scrolling in this way is unnatural too.
You may want to rethink your layout design, is it anything that couldn't be achieved with an expandable list?

While David's answer works, it has a downside. It passes ScrollView's MotionEvent object directly to HorizontalScrollView.onTouchEvent(), so if HorizontalScrollView or its children try to get the event coordinates, they will get the wrong coordinates which based on ScrollView.
My solution:
public class CustomScrollView extends ScrollView{
/*************skip initialization*************/
#Override
public boolean onInterceptTouchEvent(MotionEvent e){
//returning false means ScrollView is not interested at any events,
//so ScrollView's onTouchEvent() won't be called,
//and all of the events will be passed to ScrollView's child
return false;
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//manually call ScrollView's onTouchEvent(),
//the vertical scrolling happens there.
onTouchEvent(ev);
//dispatch the event,
//ScrollView's child will have every event.
return super.dispatchTouchEvent(ev);
}
}
Just wrap this CustomScrollView around the HorizontalScrollView in your layout file.

Related

onFling and LinearLayouts

My Linear Layout: main_linear_layout contains four Linear Layouts: ll1 ll2 ll3 ll4. Each LinearLayout contains Buttons. I am trying to implement the onFling method on main_linear_layout. I want to be able to swipe anywhere on the Layout of Buttons and call an action.
Currently I am able to swipe anywhere on the screen except for the Layout of Buttons. I tried using the following code to fix the problem:
swipeLayout = (LinearLayout) findViewById(R.id.main_linear_layout);
//swipeLayout is the main Layout that contains 4 other linear layouts
swipeLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
gestureScanner.onTouchEvent(event);
return true;
}
});
But I am still not able to swipe on the Layout of Buttons. Does anybody know why this is? Is there something else I need to implement? Am I supposed to implement an OnTouchListener to every Linear Layout inside the main Linear Layout? Or to every Button in each Layout?
And in the xml I added a bunch of code to the main Linear Layout:
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:longClickable="true"
But this did not work either. Can someone please help?
If you have a LinearLayout, and then more layouts inside of that, and then buttons on those as you say you do, then this is functioning as intended.
You're only adding the listener to the outside layout...so of course, it'll only trigger when you slide on it. By sliding on the other layouts, or even the buttons, the slide event doesn't even get to that listener, because it is consumed.
You'd need to add the listener to each element that you want to be checking for.
One way to do this is to create an array of your buttons and do it all at once:
private Button button1;
private Button button2;
....
private Button button10;
...
protected void onCreate(Bundle b) {
super.onCreate(b);
button1 = findViewById(R.id.button1);
...
button10 = findViewById(R.id.button10);
OnClickListener onCL = new OnClickListener . . . //do all of your creation here
Button[] buttons = {button1, button2, . . . , button10};
for(Button b : buttons) {
b.setOnClickListener(onCL);
}
}
Well the reason the ll1,ll2,ll3,ll4 don't pick up on touch events is because the parent linear layout receives the motion event and it doesn't propagate further.
Why do you need a touch event listener on the main linear layout? Do you want the sub layouts to all fling together?
If you want the other layouts to pick up on the touch event return false on the touch listener
swipeLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
gestureScanner.onTouchEvent(event);
return false;
}
});
Or you could create 4 GestureDetectors and pass the event to the right one depending on which view was pressed
swipeLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(v.getId()){
case R.id.ll1:
gesture1.ontouchEvent(e);
break;
case R.id.ll2:
gesture1.ontouchEvent(e);
break;
}
//etc
return false;
}
});

Prevent Click-through of Android ImageVIew?

I have an ImageView overlay inside of a RelativeLayout and want to prevent any clicks from going through the ImageView to the Buttons etc that are behind it (thus in effect disabling the entire RelativeLayout).
Is the a simpler way of doing this then iterating the RelativeLayout views and setting them to disabled as I currently am doing using this code:
RelativeLayout rlTest = (RelativeLayout ) findViewById(R.id.rlTest);
for (int i = 0; i < rlTest.getChildCount(); i++) {
View view = rlTest.getChildAt(i);
view.setEnabled(true);
}
you can set the image to be
android:clickable="true"
Simply call rlTest.setClickable(false). This will prevent the click to be propagate to the children
There is a much cleaner way
You can use:
android:onClick="preventClicks"
in XML and in your Activity
public void preventClicks(View view) {}
This works with fragments.
Example inside this Activity has multiple fragments overlapping one another, just by adding the XML attribute in the background of your fragment it will still call the Activity.preventClicks and will prevent touches on fragments behind it
The following solution works in the general case:
_container.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// NOTE: This prevents the touches from propagating through the view and incorrectly invoking the button behind it
return true;
}
});
It basically blocks any touches from propagating through the view by marking the touch event as handled.
This works on both UI controls and layout containers (ie: LinearLayout, FrameLayout etc.).
The solution to set "clickable" as false did not work for me for layout containers either in code or in the view XML.
I assume that you are using onClickListeners.
How about using onTouchListener instead of onClickListeners. By doing this you will have a control over how deep down in your hierarchy the touch even can be visible. For example, if you have toch listeners on a relative-layout(RL) and a image-view(IV)(contained in RL), and you assign touchListeners to both. Now if you return true from IV's touch event, the lower down member RL will not receive the touch event. However if you return false from from IV's touch event, the lower down member RL will receive the touch event.
Hope this helps!
Just add these two listeners:
// Set an OnTouchListener to always return true for onTouch events so that a touch
// sequence cannot pass through the item to the item below.
view.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
v.onTouchEvent(event);
return true;
}
});
// Set an OnHoverListener to always return true for onHover events so that focus cannot
// pass through the item to the item below.
view.setOnHoverListener(new OnHoverListener() {
#Override
public boolean onHover(View v, MotionEvent event) {
v.onHoverEvent(event);
return true;
}
});
You could use databindings and consume the clicks like this:
android:onClick="#{() -> true}"
In C#, I use an empty delegate:
objectName.Click += delegate {};
I haven't encountered any problems with it but it does prevent clicks from filtering through to underlying controls.
you can also se the root click listener to null:
// Do not process clicks on other areas of this fragment
binding.root.setOnClickListener(null)
This works 100%.
It doesnt affect other listeners that are already set on the fragment's views.

How i can load new elements when my ListView is scrolled to bottom?

You can find my code here:
How to correctly build table with data using onPostExecute and ListView
I need to do loading data from the server when my ListView is scroll to the bottom. I tried to looking for solution on Stackoverflow, but it is not helpful for me.
Also if it's not difficult i like to know how it's work for understand all.
Thank's to all
You have 2 solutions:
1) With OnScrollListener
You must have a class that extends ListView and implements OnScrollListener.
When you initialise the view, set it as the scroll listener :
setOnScrollListener(this);
Implement the method onScroll. It's called when you scroll with the arguments firstVisibleItem, visibleItemCount and totalItemCount.
When firstVisibleItem+visibleItemCount==totalItemCount you reached the bottom of the list, you can call your AsyncTask again to load the next items.
2) With a custom adapter
In the method getView that you must overwrite, you have access to the position of the item being rendered, i.e. about to be visible on the screen.
Let's say you store your items in a List items you know when you reached the bottom of the list when position == items.size()-1. You can then call your AsyncTask.
Warning
Be careful with these 2 solutions, if all the items of the list fit in the screen, your AsyncTask may be called very often and for no reason. You must do the necessary checks for that before starting it.
Use the Scroll state change listener in your program...I hope It will help you definitely ....
listStudies.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (Logic Condition) {
//Here also You can do your Logic here and then you can achieve your wishes.....
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (Logic Condition) {
//You just do your Logic here and then you can achieve your wishes.....
}
}
});

How to draw a view on top of everything?

I want to make an app that can create notification on the screen on top of anything that is currently being displayed. Something like the Go SMS message popup or something like the ChatHead in the following picture:
It would be even better if it is possible to draw it dynamically including touch events.What is the conventional or standard way to do this?
Example:
Like an Icon that can be clicked or dragged no matter whether you are on home screen or app drawer or other apps.Pay attention to the circular icons near the edges of the screen in the picture posted. You can drag them anywhere in any app.
What you are looking for is System Alert Window.
There's a library called StandOut! which will assist you in creating such apps.
Here is how things like Toast and dialog windows work:
In the case where just adding or bringing to front does not work, say when you are having a service add its own view to another client activity or application (FaceUnlock does this), or you cannot depend on hierarchies, you need to use the window manager and a window token to do the trick. You can then create layouts and take advantage of animations and hardware acceleration as before.
WindowManager windowManager = (WindowManager) getBaseContext().getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.FIRST_SUB_WINDOW);
layoutParams.width = 300;
layoutParams.height = 300;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.flags =
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
layoutParams.token = getWindow().getDecorView().getRootView().getWindowToken();
//Feel free to inflate here
mTestView = new View(this);
mTestView.setBackgroundColor(Color.RED);
//Must wire up back button, otherwise it's not sent to our activity
mTestView.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
}
return true;
}
});
windowManager.addView(mTestView, layoutParams);
Then be sure to remove the view onDestroy (or onPause) or you will crash
if (mTestView != null) {
WindowManager windowManager = (WindowManager) getBaseContext().getSystemService(Context.WINDOW_SERVICE);
if (mTestView.isShown()) {
windowManager.removeViewImmediate(mTestView);
}
}
You don't need a new activity to do this. All you need to do is to add another view into your existing activity and bring it to the front, and draw/write the things that you want into that view.
If you want to do special things with this extra view, you could create your own view class
class DrawOnTop extends View {
public DrawOnTop(Context activity) {
super(activity);
}
#Override
protected void onDraw(Canvas canvas) {
// put your drawing commands here
}
}
and then you do something like
DrawOnTop mDraw = new DrawOnTop(this);
addContentView(mDraw, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
mDraw.bringToFront();
Then to force it to draw, you need to use mDraw.invalidate();
You could have the parent of your whole layout as RelativeLayout. The first child being the "root" of your main layout. Anything after that can be considered an overlay which is placeable to your whims.
Example:
<RelativeLayout>
<LinearLayout>
... Main Layout here ...
</LinearLayout>
<TextView left="20dip" top="20dip" text="Overlay" alpha="0.7" />
</RelativeLayout>
The best way is to start a service with your application.
Create an ImageView.
Set the LayoutParams of the Image View.
Add the view along with the params to the window manager when the service is created.
ALL SET
Your Image sticks to your window (At any screen over all apps), till you application is closed.
You can even add onclicklisteners and ontouchlisteners to the imageview.
Eg. OnClick listeners to perform some actions and Ontouchlisteners move the image along the screen.

Why does LinearLayout child getWidth method return 0?

I have a LinearLayout, and this LinearLayout will hold dynamically placed views. I need to find out what the width of the children of LinearLayout, however this has to be done in onCreate method. From researching I've found out that you can't use getWidth from this method. So instead I'm using onWindowFocusChanged, which works for the parent LinearLayout (returning me the actual size), but it doesn't work with its children.
Another thing I noticed is that when the screen is fading away and the screen is locked, I can see at the logs the actual width of the children being returned (I think the activity is being paused).
I'm really stuck and this is needed because I need to dynamically place those views depending on the children width.
You might be able to get with the below. But as others pointed out, this probably isn't a great idea.
LinearLayout.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
LinearLayout.getMeasuredWidth();
inside the onCreate , views still can't know the state of the nearby views and the children ,etc... so only after all is prepared and the layout process is done , you can get the size of views .
here's a quick code for getting the size of the view just before it's being drawn:
private static void runJustBeforeBeingDrawn(final View view, final Runnable runnable)
{
final ViewTreeObserver vto = view.getViewTreeObserver();
final OnPreDrawListener preDrawListener = new OnPreDrawListener()
{
#Override
public boolean onPreDraw()
{
Log.d(App.APPLICATION_TAG, CLASS_TAG + "onpredraw");
runnable.run();
final ViewTreeObserver vto = view.getViewTreeObserver();
vto.removeOnPreDrawListener(this);
return true;
}
};
vto.addOnPreDrawListener(preDrawListener);
}
alternatively , you can use addOnGlobalLayoutListener instead of addOnPreDrawListener if you wish.
example of usage :
runJustBeforeBeingDrawn(view,new Runnable()
{
#Override
public void run()
{
int width=view.getWidth();
int height=view.getHeight();
}
});
another approach is to use onWindowFocusChanged (and check that hasFocus==true) , but that's not always the best way ( only use for simple views-creation, not for dynamic creations)
EDIT: Alternative to runJustBeforeBeingDrawn: https://stackoverflow.com/a/28136027/878126
https://stackoverflow.com/a/3594216/1397218
So you should somehow change your logic.

Categories