I'm trying to use list.pointToPosition to determine which list item position the user's finger is touching. My list contains a TextView followed by a ListView in a RelativeLayout. The problem that I've discovered is that pointToPosition seems to ignore the fact that I've got a TextView and returns the wrong position of the item.
Let me explain further with some pictures and some samples of what's being returned. I've enabled the debug feature that let's me track finger touches. You'll see it in the blue line in the following images.
In the following image, you'll notice at the top of the screen is a TextView labeled "LIGHTS". Just below that is my ListView. Look at the blue line that tracks my finger touch and slide - you'll notice that it starts near the top of the row labeled "Front Porch". Performing "pointToPosition" at that location returns the correct value of 4.
Now however, if I start my touch half way down that same row, pointToPosition returns a value of 5. You can see my touch/drag in the following screen shot.
When I change the size of my TextView to have a height of 1dp, the problem goes away; thus I'm lead to believe that the pointToPosition method assumes that my ListView is the only thing on the page.
I could implement a hack and deal with this by calculating the offset caused by the TextView, but my feeling is that this will eventually come back to bite me when this bug (assuming it's a bug) gets fixed. What I'm wondering is if there's a proper way to fix it.
Source Code Samples:
layout.xml
<com.mls.util.RelativeLayoutTouchInterceptor xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:background="#drawable/app_bg"
android:id="#+id/rlRoot" android:clickable="true"
tools:context=".DeviceListActivity" >
<TextView android:id="#+id/list_header"
style="?android:listSeparatorTextViewStyle"
android:text="Lights"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TextView>
<ListView
android:id="#+id/dlList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="65dp"
android:layout_below="#+id/list_header"
android:layout_alignParentLeft="true">
</ListView>
</com.mls.util.RelativeLayoutTouchInterceptor>
RelativeLayoutTouchInterceptor.java
public class RelativeLayoutTouchInterceptor extends RelativeLayout {
...
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN: {
downStart = MotionEvent.obtain(event);
ListView list = (ListView)this.findViewById(R.id.dlList);
int position = list.pointToPosition((int)downStart.getX(), (int)downStart.getY());
}
...
}
The reason this happens is because you're overriding the onInterceptTouchEvent of the RelativeLayout, not the ListView. It's reporting the y coordinate within the layout itself, which includes the TextView.
If you want to get the coordinates relative to the ListView only, you could just override it for the ListView itself. It is an indirect subclass of ViewGroup, so it has access to the method.
There's no bug that will be fixed to worry about, it's working like it should.
I went ahead and added the perceived hack to fix this of adding the height of the TextView to the Y position provided to pointToPosition
Related
as the title says i want to understand what that method exactly is doing.
firstable i have double checked that the coordinate system of android works like that:
coordinate system
secondary - please take a minute to check my android studio screen, and the method quick doc. why is that value of my view (after clicking it) is 106?
android screen
the coordinate system is correct.
getY() will return the value of the top of your View + the Y translation. so, if getY() is returning 106 and you set translation y to 10, the top of your view should be at 96. try calling also getTop() and check what is that value
the translation is an offset that is applied to the position of the View. if the layout place your View at x;y and you call setTranslationY(10), your View will appear at x;y+10. it's a way to control the positioning of the View after the layout
bonus tip, instead of logging everything, use the debugger
in case you still have doubts about the difference between the position and translation, you could try this, create an empty activity and set this layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.lelloman.dummy.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="asd asd"
android:layout_above="#+id/button"
android:layout_centerHorizontal="true"/>
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="asd"/>
</RelativeLayout>
then, you will see that the TextView is right above the Button, because this is how the RelativeLayout will position the Views given these layout parameters. now, try to call
findViewById(R.id.button).setTranslationY(100);
you will notice that the button will be moved down by 100px, but the TextView will still be at the old position, because the translation is applied after the layout. it is something specific to that View that is not taken into account for the positioning of the View within its parent
you could also set the translation in the xml with
<Button
android:translationY="100px"
...
Just to be clear, we're talking about an XML rotated view here, not the effects of rotating the device. I have a SlidingDrawer that contains a ListFragment. The ListFragment implements the Filterable interface so that users can search its contents by providing a string input through an EditText.
The relevant layout is included below. Because the SlidingDrawer class was deprecated in API 17, the source code was copied over an accessed via a local class. That's why the name of that view looks like a custom class when really it's not.
<com.example.echo.views.SlidingDrawer
android:id="#+id/left_sliding_drawer"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:handle="#+id/left_handle"
android:orientation="horizontal"
android:content="#+id/people_fragment_container"
android:rotation="180">
<LinearLayout
android:id="#id/left_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/people_tab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/people_map_tab_grey"
android:rotation="180"
android:contentDescription="#string/people_tab"/>
</LinearLayout>
<FrameLayout
android:id="#+id/people_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.example.echo.views.SlidingDrawer>
What happens is, when the users provides input and filters the list such that there are no matching results, i.e., the ListView is empty and size is 0, the entire SlidingDrawer disappears.
Some things I've noticed in trying to fix this:
I am pretty sure this is related to displaying the empty view and/or whatever layout change occurs when it is displayed. If I simply do not set an empty view for the ListFragment the issue does not occur.
I am also pretty sure the effects are related to the fact that the SlidingDrawer is being rotated by 180 degrees since, if I remove the rotation attribute, the issue also does not occur. However, because SlidingDrawer in its default state only opened right to left, this drawer applies the XML attribute android:rotation="180" to flip the view so that it can be opened left to right. This must remain since there is other stuff on the right side of the screen that cannot be moved.
I'm not sure what is making the view disappear or where to start fixing it. I've trying fixing the child views' sizes by overriding onMeasure, onSizeChanged, and onLayout but cannot find anything that solves the issue.
Any ideas are appreciated.
I'm making an app with lots of ImageViews and I needed to attach a touch listener in some of them. While doing this, I encountered a problem. If the pointer was held down at the location where the ImageView with touch listeners attached to it and was about to produce a scrolling event, there seems a fighting scene between the Views and the ScrollView in w/c where the event was actually occured and in w/c the event is supposed to belong. The screen scrolls in a fast rate then return where the first pointer was touched down, so it means it is an unwanted behavior.
Setting an onTouchListener in the ImageView makes the scrolling uneasy and unwanted, how do I prevent ImageView from receiving touch events when scrolling?
Some of the codes
Layout: act_map_view.xml
<?xml version="1.0" encoding="utf-8"?>
<com.nkraft.mobiletomblocator.CustomVScroll
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/vScroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none" >
<com.nkraft.mobiletomblocator.CustomHScroll
android:id="#+id/hScroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="none" >
<com.nkraft.mobiletomblocator.CustomGridLayout
android:id="#+id/map_grid"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</com.nkraft.mobiletomblocator.CustomGridLayout>
</com.nkraft.mobiletomblocator.CustomHScroll>
</com.nkraft.mobiletomblocator.CustomVScroll>
Notes:
The GridLayout is populated with ImageViews at runtime.
I customized my horizontal and vertical ScrollView so that I can scroll in both directions concurrently i.e diagonal scrolling
From the notes I've mention, I figured a clue from the problem I'm currently facing. I think this issue had been thought before the standard android's scrollbar was implemented. The main reason this occurs to me is that I extended the class of the scrollbars and totally replacing the default behavior of it. So for me to achieve what I want (the scrollbar with diagonal scrolling) I follow this post and replace both my vertical and horizontal scrollview with this TwoDScrollView, also thanks for this post directing me to it.
I'm working on the following TextView:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/tabsContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="3dp"
android:background="#drawable/rounded_edges"
android:gravity="bottom"
android:padding="8dp"
android:scrollHorizontally="false"
android:scrollbarStyle="insideOverlay"
android:scrollbars="vertical"
android:textColor="#FFFFFF"
android:textSize="11sp"
android:typeface="monospace" />
This TextView is used to display large amount of data that I get from a Socket, so the content is updated by a background Service. The problem is that after certain amount of text, the scrollbar is enabled and each time I append a line to the TextView, the scrollbar starts to show and fade out every time a new line is appended to the TextView, as its gravity is set to bottom, so it gets annoying for the user.
What I want to achieve is to enable the visibility of the scrollbar only if the user scrolls manually up or down, but haven't found anything so far to implement this. What I've tried is:
Setting the android:scrollbars to none in the layout and implement a onClickListener, so it would enable the scrolling to vertical and set to none again on release, but as this event is triggered once the user releases the screen, it didn't work.
Same on onLongClickListener, same result as when a new line is appended to the TextView, the layout is set to the bottom of it as the gravity is bottom, so actually this listener is barely triggered.
Same on onDragListener, I couldn't even achieve this to trigger, so I guess this is not recognized as a drag action.
This class doesn't implement the onScrollListener.
At this point I'm out of ideas on how to implement this, so any advice is appreciated. Thanks.
You can disable the vertical scroll bar before appending new text to the TextView, and post an event to reenable it after the text has been drawn.
Something like this:
textView.setVerticalScrollBarEnabled(false);
textView.append("New Text");
textView.post(new Runnable() {
#Override
public void run() {
textView.setVerticalScrollBarEnabled(true);
}
});
Of course you should reuse a single Runnable object for enabling the scroll bar, instead of creating a new one on every change to the text.
Note that if you set the TextView gravity to bottom, then it will be constantly scrolled to the bottom whenever the text is changed, regardless of any scrolling done by the user in the interim.
I have the following layout structure in my xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainlayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<MyEntireView
android:id="#+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</MyEntireView>
<FrameLayout
android:id="#+id/focusedlayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<MyDetailedView
android:id="#+id/focus"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</MyDetailedView>
</FrameLayout>
</FrameLayout>
MyEntireView is a map (a Drawable) and MyDetailedView pops up on top of it upon onTouch() and contains only a certain piece of the whole map (also Drawable) that displays in greater detail the area which the user touched on the entire map in MyEntireView.
When MyDetailedView is shown - i.e. its setVisibility(View.VISIBLE) - there appear some transparent shapes (also some Bitmaps) on top and are redrawn regularly (e.g., 1 sec interval). The shapes are added dynamically as children of the #+id/focusedlayout as there is always a different number of them and their position is relevant to the detailed map picture.
And the problem is that each single update is processed multiple times. Each time a shape updates, then MyEntireView's onDraw() is called, MyDetailedView's onDraw() is called, and then these two are called again. And only after this the shape itself is invalidated.
As a result, this behaviour makes the shape flicker, since MyDetailedView redraws and hides the shape for 10ms or so.
I must probably be overlooking some simple logic, but I really can't get if there is a way to force only one View of a ViewGroup invalidate().
Or maybe there is an alternate structure/layout that I could use to fit my purpose?
Thanks a lot!
P.S.
The entire code is quite big to put here but I'm willing to insert any pieces upon request