Android: Passing touch event to overlapped view - android

I have a Relative layout which has two child layouts. First child is linear layout which is container of a fragment. The fragment has few buttons. The second child is a linear layout which has a View which is blank and transparent. The second child overlaps the first child.The first child is smaller than second child. I want to send touch events from second child to first child so that those buttons on the fragment which first child contains receives click.
I read few posts on internet for solving my problem but could not solve it.
So far i have overriden dispatchTouchEvent of Activity and have tried to detect whether touch is in bounds of first child and if so i do firstChild.onTouch(ev). Actually i just dont know what to do and just trying get it working. So please, help me.
#Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
float x,y,backViewX,backViewY,frontViewX,frontViewY;
x = ev.getX();
y = ev.getY();
frontViewX = frontView.getX();
frontViewY = frontView.getY();
backViewX = backView.getX();
backViewY = backView.getY();
if(y >= backViewY && y <= (backViewY+backViewHeight))
{
if(x >= backViewX && x<=(backViewX+backViewWidth))
{
return backView.onTouchEvent(ev);
}
}
return super.dispatchTouchEvent(ev);
}
Orignal Layout is not the exact as i described but it is little more complex but the concept is same as i described....i am shortning the layout for simplifying things.The frontLayout here overlaps layoutBackFragmentArea.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/back_view_background">
<LinearLayout
android:id="#+id/layoutBackFragmentArea"
android:layout_width="match_parent"
android:layout_height="#dimen/back_view_height"
android:orientation="vertical"
android:gravity="center_vertical"
android:background="#android:color/transparent">
</LinearLayout>
<LinearLayout
android:id="#+id/layoutFrontArea"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_vertical"
android:background="#android:color/transparent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</RelativeLayout>
layoutBackFragmentArea will contain a fragment which has UI which should receive click events.

Related

Scrollviews synchronisation issue

I wrote a small app displaying 2 scrollViews next to each other. I need the scroll position of the 2 scrollViews to be synchronised. For that, I extended ScrollView and I overrode onScrollChanged to be notified when a scroll occurs, and then to sync the 2 scrollViews.
My two scroll views display a bunch of blue Views. The left scrollView has a red background, and the right has a green background.
Here is what happens with a scroll on the left scrollView:
=> The synchronisation is ok
And here is what happens with a scroll on the right scrollView:
=> The synchronisation is not good, there's a gap
(both screenshots were taken during the scrollviews' fling)
How to have a good synchronisation in both case?
The code of my Activity, my scrollView and my scrollView container is here.
It seems like you hit a system limitation here - the system draws the views in order they are declared in your XML.
Therefore, when you fling the first declared ScrollView, the second one gets updated but the first one is not updated again. When you fling the second one, however, the first one gets updated, then the second one gets updated, the change is reflected to the first one and it gets updated again.
I'm not sure that the above description is 100% accurate, but it is something along these lines.
I created a test case to check my hypothesis substituting the following for main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/main_layout">
<View android:id="#+id/separator_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
<com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_toEndOf="#id/separator_view"
android:background="#android:color/holo_green_light"
android:id="#+id/left_scrollview">
<com.example.www.syncscrollviewtesting_stackoverflow.Container
android:layout_width="match_parent"
android:layout_height="100000dp"
android:minHeight="100000dp"
android:id="#+id/left_container"/>
</com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView>
<com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_toStartOf="#id/separator_view"
android:background="#android:color/holo_red_dark"
android:id="#+id/right_scrollview">
<com.example.www.syncscrollviewtesting_stackoverflow.Container
android:layout_width="match_parent"
android:layout_height="100000dp"
android:minHeight="100000dp"
android:id="#+id/right_container"/>
</com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView>
</RelativeLayout>
The above XML allows you to position both ScrollViews on either sides of the separator_view. I found out that no matter how you position them, a fling of the ScrollView having red background (declared second) always causes the "lag", while a fling of the ScrollView having green background works fine.
I also tried to prevent unnecessary updates of the ScrollViews by adding this to their code:
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (!mIsDisabled && scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
public void setDisabled(boolean isDisabled) {
mIsDisabled = isDisabled;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (mIsDisabled)
return false; // Ignore touch event when disabled
else
return super.onTouchEvent(ev);
}
... and this to Activity's:
#Override
public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
if (scrollView == mRightScrollView) {
mLeftScrollView.setDisabled(true);
mLeftScrollView.setScrollY(y);
mLeftScrollView.setDisabled(false);
} else {
mRightScrollView.setDisabled(true);
mRightScrollView.setScrollY(y);
mRightScrollView.setDisabled(false);
}
}
but it didn't help either...
So, I guess, you better find another approach which does not involve redrawing a whole lot of Views, or just accept the "lag".
Solution:
This solution was provided by the OP himself, based on my analysis of the situation: touch events can be forwarded from the right ScrollView (declared second in XML) to the left ScrollView. This way, given that flings on the left ScrollView do not cause lags, all touch events are treated as being initiated by the first declared ScrollView and the lag is avoided.
Apologies for my previous answer hadn't properly studied your question.
As I've said, I don't think you can sync 2 ScrollViews properly without getting any latency at all. However you can still use a single view.
This gets a little trickier because you also want to split your split into equal parts with weight attribute.
The solution can be found here. Building up on that I've come up with the following code:
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="#+id/spacer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"/>
<LinearLayout
android:id="#+id/left"
android:layout_alignRight="#id/spacer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
</LinearLayout>
<LinearLayout
android:id="#+id/right"
android:layout_alignLeft="#id/spacer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
</LinearLayout>
</RelativeLayout>
</ScrollView>
Then when you want to toggle the left and right view overlapping call this:
leftParams.addRule(RelativeLayout.ALIGN_RIGHT, 0);
rightParams.addRule(RelativeLayout.ALIGN_LEFT, 0);
and
leftParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.spacer);
rightParams.addRule(RelativeLayout.ALIGN_LEFT, R.id.spacer);
after setting the params you need to redraw the view with:
left.requestLayout();
right.requestLayout();
A working example can be found on this GitHub page.

Android: ScrollView scrolls to the top inadvertently

I've got such a layout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:id="#+id/scroll_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:fillViewport="true" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
other views
...
<TextView
android:id="#+id/text_view_that_can_change"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
</ScrollView>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
layout="#layout/btns_ok_cancel" />
</RelativeLayout>
The problem is that when I add text to initially empty text_view_that_can_change - my ScrollView scrolls to the top.
The same problem happens when I set visibility to some of views inside ScrollView/RelativeLayout.
I can scroll it back using scrollView.scrollTo(0, yScroll) but it gives a visible and, I must say, ugly jerk to the whole content.
It there any way to prevent this behavior?
Have spent some hours and here is what I've discovered.
Scroll to the top happens because inner RelativeLayout is being re-laid out as a result of the content change.
You should add a couple of lines to control this situation:
1) declare class member that will hold scroll position:
int mSavedYScroll;
2) Add GlobalLayoutListener to the main view (here it's a fragment's main view, but that doesn't matter):
public View onCreateView(...) {
mView = inflater.inflate(...);
...
mView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (mSavedYScroll != 0) {
mScrollContainer.scrollTo(0, mSavedYScroll);
mSavedYScroll = 0;
}
}
});
}
3) When you do something (anywhere in you code) that may change content of ScrollView - just save current scroll position:
mSavedYScroll = mScrollContainer.getScrollY();
//DO SOMETHING THAT MAY CHANGE CONTENT OF mScrollContainer:
...
That's it!

Detcting horizontal swipe on relative layout and also properly handle scroll event on the relative layout without any interception

I am new to android development and I am stuck upon following problem. I have been working hard for days and not able to find a solution. In this question I am explaining what steps that I have exactly taken to implement. Please have a look at my procedure and either suggest a solution or an improvisation or an alternative way through which this can be handled.
I want to design a screen which is divided into three parts. The structure of my screen is as follows :
The parent layout of the whole screen is a Scroll-View. The rough structure of the layout file is :
<ScrollView>
<!-- Other Views (Part: 1) -->
<RelativeLayout>
<!-- Part 2 -->
<LinearLayout android:orientation = "vertical">
<LinearLayout android:orientation = "horizontal">
<Button1 /> <Button1 /> </Button3>
</LinearLayout>
<LinearLayout android:orientation = "horizontal">
<Button4 /> <Button5 /> </Button6>
</LinearLayout>
</LinearLayout>
<ImageView>
</RelativeLayout>
<!--Other Views (Part 3) -->
</ScrollView>
The part of interest is the middle part that is the Relative layout. The Things that I want to
Implement is that :
I want to detect a swipe on this RelativeLayout. So with every swipe to either Left or
Right I update the the images and the text that is there on these buttons.
I want to be able to scroll the screen from anywhere on the screen.
To be able to click on the buttons.
Now let me tell you what I have done and the problems that I am facing. To detect the swipe on the relative layout I have set an onTouchListener on the relative layout. The overridden onTouch method is :
public boolean onTouch(View v, MotionEvent event) {
scrollview.requestDisallowInterceptTouchEvent(true);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downX = event.getX();
downY = event.getY();
return true;
}
case MotionEvent.ACTION_UP: {
if(v instanceof Button){
v.performClick();
}
upX = event.getX();
upY = event.getY();
float deltaX = downX - upX;
float deltaY = downY - upY;
// swipe horizontal?
if (Math.abs(deltaX) > MIN_DISTANCE) {
// left or right
if (deltaX < 0) {
this.onLeftToRightSwipe();
return true;
}
if (deltaX > 0) {
this.onRightToLeftSwipe();
return true;
}
}
}
}
return false;
}
Since , the buttons occupy all the space on the relative layout , I had to set this Listener on
all the inner Linear Layouts and Buttons. To detect the button click and disallow the scroll view to intercept I had to include scrollview.requestDisallowInterceptTouchEvent(true) .
But the problem is that now I am not able to scroll on this Relative layout.
The Swipe and Button clicks are working exactly as they should but , now it is not detecting the scroll.
If I remove the scrollview.requestDisallowInterceptTouchEvent(true) , the scroll is detected but the swipe is not . This is giving a very bad user experience and overall a negative feedback from all of those to who I have shown the application.
So what i want is that , user should be able to scroll from any point of the screen plus he should be able to swipe on the relative layout and also be able to click on the buttons.
Please Help! Could the possible solutions be like :
1) implementing a simpleOnGesture Listener or Detector? override fling event? Please provide valuable and useful pointers.
I suggest you to use ViewPager to accomplish this. You can learn more about ViewPagers from this link: http://developer.android.com/training/animation/screen-slide.html
And there is also a sample code in the website that you can see how it works. It allows you to swipe through pages and also scroll down in each page.
So the main.xml file looks like this:
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And the fragment.xml file looks like this:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="#android:id/text1"
style="?android:textAppearanceLarge"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp" />
<TextView style="?android:textAppearanceMedium"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/lorem_ipsum" />
</LinearLayout>
</ScrollView>

ListView into ScrollView (pull-to-refresh)

I have to add pull-to-refresh functionality to refresh info on main screen. Here`s a scheme of my screen UI (red area should handle pulling):
I use ready solution for pull-to-refresh. Due to documentation, my red layout should be one of these classes:
ListView
ExpandableListView
GridView
WebView
ScrollView
HorizontalScrollView
ViewPager
But I have ListView on my screen, so I am not able to use ScrollView as red layout. And I`m stuck with this problem. It is possible to use UITableView into UIScrollView in iOS, but in Android one I have no idea what to do in such cases.
Any help will be appreciated. Thanks!
Why not replace the listview by a LinearLayout and then you can use the ScrollView?
You just need to create a layout for the items in the linearlayout and then adding them using something like this:
LinearLayout layout = (LinearLayout)findViewById(R.id.MyListLayout);
for (int i=0; i<list.size(); i++) {
Item item = list.get(i);
View view = inflater.inflate(R.layout.MyRowLayout, null);
Textview myTextView = view.findViewById(R.id.MyTextView);
myTextView.setText(item.getString());
list.addView(vi);
}
Is the pull-to-refresh horizontal or vertical?
In case it's horizontal, use a HorizontalScrollView (a ViewPager would do, too), and then place a Table?Layout inside it.
In case it's horizontal, well, I don't think I like that design (the pull-to-refresh area should just be the ListView), but I believe there is some way to use the ListView without its internal scrolling so that you can rely on a parent ScrollView to do the scrolling, but I'd need to check the code of an old problem to "remember" how to do that.
I've solved this issue, but forgot to write about it :)
My layout.xml file looks like this:
<com.handmark.pulltorefresh.library.PullToRefreshScrollView
android:id="#+id/home_info_pulltorefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true" >
<LinearLayout
android:id="#+id/home_info_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white_background"
android:baselineAligned="false"
android:orientation="horizontal" >
<LinearLayout
android:id="#+id/charts_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="vertical"
android:paddingRight="5dp" >
<FrameLayout
android:id="#+id/frame_container_chart_top"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<FrameLayout
android:id="#+id/frame_container_chart_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<FrameLayout
android:id="#+id/frame_container_right"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</com.handmark.pulltorefresh.library.PullToRefreshScrollView>
android:fillViewport="true" is used to stretch its content to fill the viewport (make height of scroll match_parent)
I add Fragment containing ListView programmatically (R.layout.frame_container_right Fragment resource ID)
Everything worked fine, but when I tried to scroll ListView down, my ScrollView began to scroll. Scrolling ListView up worked fine. Also I noticed that if I tap ListView, move finger left or right and then try to scroll down, touch events are not transmitted from ListView to ScrollView and I get expected behavior. So I've decided to emulate this situation programmatically:
mListViewReviews.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
long startTime = SystemClock.uptimeMillis();
long duration = 1;
MotionEvent e = MotionEvent.obtain(startTime, startTime + duration, MotionEvent.ACTION_MOVE, event
.getX(), event.getY() + 10, 0);
MotionEvent ev = MotionEvent.obtain(startTime + duration, startTime + duration * 2,
MotionEvent.ACTION_MOVE, event.getX(), event.getY() + 20, 0);
v.dispatchTouchEvent(e);
v.dispatchTouchEvent(ev);
}
return false;
}
});
As a result, I've got working ListView with proper cell reuse and working pull-to-refresh logic. Thanks!

how to disable dispatch touch event in some portion of screen

I want to disable dispatch touch for some area for your understanding here is my screen.
You can see Header,Footer and Mapview in image but when I clicked on location button(right side in header), My map is also getting notified (like it got touched) which I don't want. I want that only onClick event of button should be clicked not dispatch event.
Here is my XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/LinearLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<!-- include title bar for all screen -->
<include
android:id="#+id/include1"
layout="#layout/titlebar_layout"
android:layout_gravity="top" />
<FrameLayout
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.88"
>
<com.google.android.maps.MapView
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.63"
android:apiKey="0VkXbAOFvAq7b6uaGHSmnS2a2VosPxoS6ceHY_g"
android:clickable="true" >
</com.google.android.maps.MapView>
</FrameLayout>
<include
android:id="#+id/include2"
android:layout_gravity="bottom"
android:layout_marginBottom="38dp"
layout="#layout/bottom_layout" />
</LinearLayout>
Any help will be greatly appreciated.
You can override the dispatchTouchEvent, and check where the user has touched the screen..
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//first check if the location button should handle the touch event
if(locBtn != null) {
int[] pos = new int[2];
locBtn.getLocationOnScreen(pos);
if(ev.getY() <= (pos[1] + locBtn.getHeight()) && ev.getX() > pos[0]) //location button event
return locBtn.onTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}
The reason why MapView is receiving the dispatchTouchEvent() events is that Android starts dispacthing the the events for the last added view, and if it's not handled there, then it's dispatched to the last the view added before that one, and so on ...
In you xml layout, you first add Header, then MapView and for last Footer. So in your case, the the events are first dispatched to Footer, then to MapView, and for last to the Header.
You have to option to solve this:
The simplest is to reorder the items in your layout, starting with mapview and then adding all the others. You may need to use relative layout to be able to position them correctly. With this MapView will not see events that are handle by the button.
Keep your layout and let MapView receive the events. MapView will need to test if event should be handled by him or ignored. For that you can use the code sugested by #Nunu, which should work. Maybe you need to add to the button Y coordinate the button height.
good luck.
Use relative layout with Map View at bottom and Button to clicked at top.. and add click event for the button
use view.bringToFront() to get the view places above other views.

Categories

Resources