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!
Related
I have set in XML visibility="gone" for the View. On the first click on the button (), the View should become visible (it works) viewView.visibility = VISIBLE. The second time you click on the button, the view should become "gone" again viewView.visibility = GONE.
But instead, the View doesn't disappear, but just becomes transparent, as if I had set invisible (look at the illustration from Layout Inspector).
How can I make the View truly "gone" like in the first frame?
Please look the illustration from Layout Inspector
Example code
var isVisible = false
val button = findViewById<View>(id.button)
val view = findViewById<View>(id.view)
button.setOnClickListener {
view.visibility = if (isVisible) { GONE } else { VISIBLE }
isVisible = !isVisible
}
And XML
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2"
android:background="#color/design_default_color_primary" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.4" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2">
<View
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/design_default_color_secondary"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
I think you're worrying too much about what's happening behind the scenes. Here's what happens if you add a TextView below that View:
Yes view still has its full size, but it's not actually affecting the layout. That's what GONE means - it's not visible, but it's also behaving as though it's not there, and everything else is laid out accordingly.
That doesn't mean the view itself has been recalculated with zero height or anything - for one thing, removing something doesn't change its height, right? It's just not in the layout anymore, so the space it's taking up is now zero. And there's also no reason to recalculate the height while the View isn't being displayed, it would be wasted work. It's just there, hanging around, waiting to be added to the layout again.
It's not like you can click it while it's in this state anyway, so what it's actually doing and how the system is managing stuff shouldn't be relevant. If your code does somehow rely on the View being "gone" in some sense (not present in the layout, garbage collected, zero width and height) then you'll have to account for determining that yourself, and whatever you're doing now isn't enough. You could check its visibility property remember!
Also just as an aside, be careful about what you name things - an ID called view and especially a variable called view inside a class that already has a view property is just asking for trouble. Same with a variable called isVisible being referenced in the scope of a Button and a Fragment, both of which have a property with the same name (and you'll get a warning about this one, accidental override)
It just leads to bugs when you accidentally reference the wrong thing. If these are just placeholder names you're using for your question, bear in mind people will copy your code to try and help you, and you're making work for them when they have to fix it. Not having a go or anything, just something to keep in mind!
try this:
button.setOnClickListener { v ->
v?.let {
val isViewVisible: Boolean = view.isVisible
view.isVisible = !isViewVisible
}
}
I really didn't want to post it here because this problem sounds really stupid and is difficult to describe, but after hiting my head 2 hours for a stupid layout problem i decided to try
I've one activity with several layout components...
During on create all components are set to be invisible just one keeps visible.
When user presses the button, all components turn visible
when presses button again, all components SHOULD turn invisible again
ALL COMPONENTS VISIBILITY IS ADJUST IN ONLY ONE METHOD
so the activity looks like:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.new_giveaway, R.id.mainView);
/*lots of stuff*/
//last thing
makeVisible(View.INVISIBLE);
}
private void makeVisible(int visi) {
findViewById(R.id.cardView).setVisibility(visi);
((ViewGroup) findViewById(R.id.influencerLayout)).setVisibility(visi);
this.recyclerView.setVisibility(visi);
}
the problem: is on second click all components get invisible but one keeps on screen
the component which stays on is a cardview
Mainlayout:
<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.tomatedigital.giveawaymaster.activity.NewGiveawayActivity">
//lots of stuff//
<include layout="#layout/giveaway" />
layout/giveaway is:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardView"
android:layout_width="match_parent"
android:layout_height="#dimen/giveawayCardHeight"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
//lots of other stuf
</cardview>
It's the first thing i set visible on controller method but the only which doesn't go back invisible
REPEATING: there is no other calls to setVisibility other than these, all visibitilities are controled just under that method
I didn't post the whole activity code here because is way long
==========UPDATE==========
Just to clarify:
1- the cardview is one separated layout file re-used several places
2- there is only one cardview in the mainlayout
3
if i remove makeVisible(View.INVISIBLE)from onCreate(), all stuff stays visible,
if i call makeVisible(View.INVISIBLE) and never call makeVisible(View.VISIBLE) all stuff stays invisible
but if I invisble->visible->invisible everything goes invisible but cardview keeps visible
When you want to set the whole layout to Invisibility state, you need to do it in your include #layout/giveaway.xml. Becouse it is a view too.
Like we talk in comments...
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 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'm developing an app with SupportMapFragment and custom view switching mechanism. Right now i styles app, to make its window to be transparent using styles.xml, but with that change i see a strange problem when using maps. When user switches it current view from one that uses map fragment to another, part of screen that was used by map on previous view becames transparent, for about 1 second.
In image below i present some description, on right i marked using yellow color situation than im talking about:
on left side is view that i present map using SupportMapFragment, on the right is one i have switched next, with 2 diffrent views(ImageView and TextView),but yellow part of TextView is transparent like my application window, su user can notice eg. apps menu. This is not what im trying to achive so Im looking way to get rid of this effect.
My code for switching views is like follows:
public void switchAppView(View view){
mMainView.removeViewAt(0);
mMainView.addView(view,0);
}
the mMainView is initialized in onCreate Method, by code:
#Override
public void onCreate(Bundle icicle){
super.onCreate(icicle);
setContentView(R.layout.activity_main);
mMainView = (RelativeLayout) findViewById(R.id.mymain_layout);
}
my activity_main.xml layout presents similar to:
<RelativeLayout
android:id="#+id/mymain_layout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<FrameLayout
android:id="#+id/replacable"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="visible"
></FrameLayout>
<FrameLayout
android:id="#+id/extra"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="invisible"
></FrameLayout>
</RelativeLayout>
I tried to search clues and situations like that but I didnt find any, so if anyone can help i would be grateful, Thanks for help.
becames transparent, for about 1 second.
I'm guessing you are using black background and you really mean it becomes black.
In that case what you see is a known issue.
You may read more about it here:
http://code.google.com/p/gmaps-api-issues/issues/detail?id=4659
and here:
http://code.google.com/p/gmaps-api-issues/issues/detail?id=4639