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.
Related
Background
I have a rather complex layout being shown to the user in an activity.
One of the views is an EditText.
Since I had to make one of the views stay behind the soft-keyboard, yet the rest above it, I had to listen to view-layout changes (written about it here).
The problem
I've noticed that whenever the EditText has focus and shows its caret, the entire view-hierarchy gets re-layout.
You can see it by either looking at the log of the listener I've created, or by enabling "show surface updates" via the developers settings.
This causes bad performance on some devices, especially if the layout of the activity is complex or have fragments that have complex layouts.
The code
I'm not going to show the original code, but there is a simple way to reproduce the issue:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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.user.myapplication.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="just some text"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone"
android:text="write here"
android:textSize="18dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="just some text 2"/>
</LinearLayout>
</FrameLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(android.R.id.content).getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
Log.d("AppLog", "onPreDraw");
return true;
}
});
}
}
What I've tried
When disabling the caret (using "cursorVisible", which for some reason is called a "cursor" instead) , I can see that the problem doesn't exist.
I've tried to find an alternative to the built-in caret behavior, but I can't find. Only thing I've found is this post, but it seems to make it static and I'm not sure as to how well it performs (performance and compatibility vs normal caret).
I've tried to set the size of the EditText forcefully, so that it won't need to cause invalidation of the layout that contains it. It didn't work.
I've also noticed that on the original app, the logs can (for some reason) continue being written even when the app goes to the background.
I've reported about this issue (including sample and video) here, hoping that Google will show what's wrong or a fix for this.
The question
Is there a way to avoid the re-layout of the entire view hierarchy ? A way that will still let the EditText have the same look&feel of normal EditText?
Maybe a way to customize how the EditText behaves with the caret?
I've noticed that whenever the EditText has focus and shows its caret,
the entire view-hierarchy gets re-layout.
This is not true. Size and position of EditText is constant - there is no re-layouting. You can check it by using code below.
findViewById(android.R.id.content).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Log.d("AppLog", "Layout phase");
}
});
Because of blinking caret - EditText constatly calls invalidate(). This forces the GPU to draw EditText again.
On my nexus 5 (marshmallow) I see that only EditText beeing redrawn (Show GPU view updates - enabled).
How about overriding dispatchOnPreDraw() all the vies you use in activity and having flag to check whether that specific view needs to redraw ?
As you need to disable redraw of all other views only when a text view is on focus. So when a text view is on focus have a flag to disable the redrawing of other views.
if dispatchOnPreDraw() method returns false then refreshing of view will be continues else not. I don't know how complex is your layout and how many views are used, but here a separate class should extent a view used and override that method, and also need a mechanism/variable to distinguish the object in current focus.
Hope this method helps!
I have implemented a SwipeRefreshLayout on my ListView, and it is working, but not how I want it to.
First of all, the refresh triggers before I lift my finger, which doesn't feel right because it moves the content back to the top even though my finger is still sitting in swiped down position. Also the distance to trigger is really short and setDistanceToTriggerSync from the docs is not available to me for some reason.
second, I'd like some sort of view the be displayed in the gap when my list view is pulled down, like a text that tells the user "swipe down to refresh" or a an animation (like the dancing ghost in snap chat). How do I set this View
Here's what I have
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/dashboardRootLayout"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/lvDashboard"
android:background="#ffffff"
/>
Why isn't the setDistanceToTriggerSync available?
Anyway the default behavior of the SwipeRefreshLayout does not have that extra view, it just has a special progress bar and a listener for refreshing and showing or not the progress of refresh. Also it can change the look of the actionbar with the refresh message.
Possible solution:
If you want that special view, of the top of my head I can think of having the list being moved down with animation and creating or putting VISIBLE the view you want to show on the top of the SwipeRefreshLayout.
SwipeRefreshLayout swipeRefreshLayout=(SwipeRefreshLayout)findViewById(R.id.dashboardRootLayout);
swipeRefreshLayout.setOnRefreshListener(new OnRefreshListener()
{
#Override
public void onRefresh()
{
// do animation
// set the view you want to show VISIBLE
}
});
You could actually implement all of this by yourself without the SwipeRefreshLayout since you don't want the default behavior.
}
I have the following code to get the entered text scolling. It scrolls but I am not able to see the scrollbar. I want to see the scrollbar visible every time irrespective of the data available
text.setSingleLine(false);
text.setScroller(new Scroller(this));
text.setVerticalScrollBarEnabled(true);
text.setHorizontalScrollBarEnabled(true);
text.setMaxLines(3);
text.setMovementMethod(new ScrollingMovementMethod());
You must set the position, style, size and fade to guarantee it to be visible. Example:
text.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
text.setScrollBarSize(5);
text.setScrollbarFadingEnabled(false);
Use following code in your layout file. This will allow you to visible scroll bars every time.
android:fadeScrollbars="false"
Or you can also use following code to do so
scrollView.setScrollbarFadingEnabled(false);
Exapme
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadeScrollbars="false">
Reference
ScrollView:fadeScrollbars
setScrollbarFadingEnabled(boolean)
Is there any way to have a button directly below a listview, so that as the listview grows, the button moves down BUT the button is never pushed off screen. IE, once the listview has outgrown the screen, the button is still always visible, and the listview is scrollable.
I have managed to make the button ALWAYS at the bottom of the screen, but i want it to sit up directly below the listview while the listview is smaller than the screen.
I have tried using various arrangements of relative and linear layouts and using the weight property, and things that seem like they should work simply don't, so it might be worth checking any answers before posting.
CLARIFICATION:
To phrase it in a different way: I want a button to sit below a listview, moving down as it grows, but i dont want the button to be pushed offscreen
This previous post does exactly what you want to do. What it does basically is that it keeps the button at the bottom of the list at all times. But when the list grows out of the screen area, its height gets limited by the weight parameter.
This way, the list's bottom edge is just above the button's LinearLayout and you get the same behavior that you were looking for.
If You Want to show this button in the end of list item. Then use this code
final Button btnAddMore = new Button(this);
btnAddMore.setText(R.string.art_btn_moreIssues);
exArticlesList = (ExpandableListView) this.findViewById(R.id.art_list_exlist);
exArticlesList.addFooterView(btnAddMore);
OR If you show button in your layout end then use this code.
<RelativeLayout
android:id="#+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/listView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/btn_New" >
</ListView>
<Button
android:id="#+id/btn_New"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_marginBottom="20dp"
android:text="#string/New"
android:width="170dp"
android:layout_alignParentBottom="true" />
</RelativeLayout>
What would cause only a portion of a list view item to highlight when tapped?
I'm working on an Android app where the user navigates through a series of lists, and some of the list items don't highlight properly. The list items have an image on the left followed by text. If the text in the item mostly fills the row, the entire row highlights when tapped. If the text is short, however, then only part of the row highlights when tapped. I'd really like the entire row to highlight regardless of the length of the text. My list item layout looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView android:id="#+id/zoneIcon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingLeft="5dp">
</ImageView>
<TextView
android:id="#+id/itemtitle"
android:layout_width="fill_parent"
android:textColor="#color/listItemColor"
android:textSize="14sp"
android:layout_gravity="top|left"
android:layout_height="fill_parent"
android:padding="13dp"
android:textStyle="bold">
</TextView>
</LinearLayout>
I thought that setting the layout_width of the text view to fill_parent would cause the text view to fill the remainder of the item's width, and that the whole row should highlight. Is there something else I need to set to control the width of the overall item? What am I doing wrong?
Edit: I also have another problem which may be related: When I scroll the list (which normally has a white background), the area occupied by the list items turns black during the scrolling, and then appears normal again when scrolling stops. The black area is the same size as the area that would be highlighted (except that it's all the items rather than just one), which is to say that there are white spaces on the right side of some of the items while scrolling. I'd like to prevent the list from turning black while scrolling, but perhaps that's another question. I mention it because it makes me think that some list items aren't filling their parent, but I'm still not sure why.
The black background is controlled by android:cacheColorHint (XML) or ListView.setCacheColorHint (Java) -- it used for drawing a solid color underneath the list while scrolling for performance reasons.
The list item should highlight in full regardless of the size of the TextView since the LinearLayout fills the parent. However you should certainly change the height of the LinearLayout towrap_content` -- it doesn't make much sense for it to fill a parent since the height of a list (and therefore the maximum size of a list item) is unbounded.
The height of the TextView and ImageView should probably be set to wrap_content as well. See if those changes resolve your issue.