I have a three tab interface setup using a ViewPager. In the 3rd tab, I have a CardView which can expand/retract onclick to reveal more information. Inside the CardView is a RecyclerView which is inflated with a bunch of data.
The first time the app opens and the CardView is expanded, the expand animation is quite laggy but afterwards, it can be expanded/retracted smoothly. The RecyclerView data can also be updated with no problem.
Is there a way to somehow load/cache the data upon opening the app so the lagg does not occur.
Sorry if question is poor, just starting android development.
Thanks
Layout on third tab:
<android.support.v7.widget.CardView
android:id="#+id/SF_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp">
<LinearLayout
android:id="#+id/SF_wrapper"
android:layout_width="fill_parent"
android:layout_height="80dp"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/SF_list"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</android.support.v7.widget.CardView>
The data which is used to inflate the recyclerView is a list of objects each with 2 float variables. Roughly length ~50.
The xml used to inflate each item:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4sp"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="36sp"
android:layout_weight="1">
<TextView
android:id="#+id/value_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="lorem"
android:textSize="8sp" />
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="36sp"
android:layout_weight="1">
<TextView
android:id="#+id/value_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="ipsom"
android:textSize="8sp" />
</RelativeLayout>
</LinearLayout>
The code to expand/retract the cardView simply animated changing the height of SF_frame to reveal more elements of the RecyclerView and it is this first reveal which is laggy.
Resolve your Layouting as first:
Use sp only with texts and dp for other dimensions (width, height, margins, paddings etc.). For texts it is recommended to use at least android:textsize="12sp". Look at the android:gravity attribute instead of android:layout_gravity so you don't have to use parent layout just to center it.
<android.support.v7.widget.CardView
android:id="#+id/SF_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp">
<!-- No need to use LinearLayout since only one child is used -->
<android.support.v7.widget.RecyclerView
android:id="#+id/SF_list"
android:layout_width="match_parent"
android:layout_height="80dp">
</android.support.v7.widget.RecyclerView>
</android.support.v7.widget.CardView>
In this layout, if you are expanding RecyclerView, it is possible that new views are being instantiated (RecyclerView.Adapter.onCreateViewHolder and RecyclerView.Adapter.onBindViewHolder) are called mulitple times.
In fact switching to that Tab is also a little bit delayed, but it is clearly visible on animation. (you are expandig from about 3 items to maybe 20 or so, I don't know how rest of your layout looks)
and second:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:orientation="horizontal">
<TextView
android:id="#+id/value_1"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_weight="1"
android:layout_gravity="center"
android:text="lorem"
android:textSize="8sp" />
<TextView
android:id="#+id/value_2"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_weight="1"
android:layout_gravity="center"
android:text="ipsom"
android:textSize="8sp" />
</LinearLayout>
If filling these 2 TextViews is so terribly slow, that you are experiencing hickups. Then you probably doing very very complex math operations (try to simplify them if possible) or you are somehow downloading data synchronously and waiting for results.
Please have a look at AsyncTask and use it while binding data... You can use it something like this (modify by yourself):
private class MyAsyncTask extends AsyncTask<Void, Void, String> {
private TextView view;
MyAsyncTask(TextView view) {
this.view = view;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
// display progressbar or something
}
#Override
protected String doInBackground(Void... params) {
// do heavyweight operation here
String result = heavyWeightOperation(params);
return result;
}
#Override
protected void onPostExecute(String s) {
// hide progressbar or something
if (view != null) {
view.setText(s);
}
super.onPostExecute(s);
}
}
Related
I have an imageView that I want to move given a specific situation.
Initially I have a Relative layout with two textViews and an imageview. The textViews are oriented with one above the other. The imageView is set
android:layout_below="#id/text_view1"
android:layout_toEndOf="#+id/text_view2".
In the logic text_view2 is removed when a specific condition is met. I want to programmatically move the imageView to the end of text_view1 when this condition is met. Essentially when text_view2 is removed, I want to set the imageView to
android:layout_toEndOf="#+id/text_view1"
I don't believe setting X,Y,Z values is appropriate here because programmatically, I don't know where the imageView will show up given different screen sizes, and densities. I just need it to move to the end of the first textView.
Take a look at RelativeLayout.LayoutParams. You will need to manipulate the layout rules in the layout params as follows:
// Make textView2 invisible
tv2.visibility = View.INVISIBLE
// Get the LayoutParams of the ImageView
val ivParams = iv.layoutParams as RelativeLayout.LayoutParams
// Change the rule to be to the right of textView1
ivParams.addRule(RelativeLayout.RIGHT_OF, tv1.id)
// Since the placement of textView2 is changing, request a layout.
iv.requestLayout()
Consider using "END_OF" instead of "RIGHT_OF".
You can either place the Views in a nested LinearLayout or use a ConstraintLayout with a Barrier.
It is generally recommended to use ConstraintLayout because nested LinearLayouts are bad for performance but since ConstraintLayout takes some getting used to, I did not want to omit the other option.
To demonstrate the two approaches I've set up a small example with a LinearLayout and a ConstraintLayout in the same screen:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.TabTwoFragment">
<LinearLayout
android:layout_width="240dp"
android:layout_height="128dp"
android:background="#cccccc">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#44ff0000"
android:maxWidth="160dp"
android:text="Upper TextView\nin\n nested\n LinearLayout" />
<TextView
android:id="#+id/longTextViewInLinearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#4400ff00"
android:maxWidth="160dp"
android:text="Lower TextView in nested LinearLayout" />
</LinearLayout>
<ImageView
android:id="#+id/linearLayoutImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="#ffffff"
android:src="#drawable/ic_android_black_24dp"
android:tint="#color/colorPrimary" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="240dp"
android:layout_height="128dp"
android:layout_gravity="bottom|end"
android:background="#666666">
<TextView
android:id="#+id/shortTextViewInConstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#44ff0000"
android:maxWidth="160dp"
android:text="Upper TextView\nin\n nested\nConstraintLayout"
android:textColor="#ffffff"
app:layout_constraintBottom_toTopOf="#+id/longTextViewInConstraintLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="#+id/longTextViewInConstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#4400ff00"
android:maxWidth="160dp"
android:text="Lower TextView in ConstraintLayout"
android:textColor="#ffffff"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/shortTextViewInConstraintLayout" />
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="shortTextViewInConstraintLayout, longTextViewInConstraintLayout" />
<ImageView
android:id="#+id/constraintLayoutImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="#ffffff"
android:importantForAccessibility="no"
android:src="#drawable/ic_android_black_24dp"
android:tint="#color/colorAccent"
app:layout_constraintStart_toEndOf="#+id/barrier"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
If the ImageView is clicked, the longer TextView will disappear and the ImageView will move closer to the short TextView. The animations are provided by Android's transition framework, so basically all you have to do is trigger the transition by calling TransitionManager.beginDelayedTransition()
For demonstration purposes, I've placed all the code in one method. Please note that normally one would have the TransitionSet as field of the Fragment so that it does not have to be recreated every time you need it. (The code is in Java since Android Studio supports automatic translation to Kotlin if required but not the other way round ;-) )
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TransitionSet ts = new TransitionSet();
ts.addTransition( new ChangeBounds());
ts.addTransition(new Slide());
View imageViewInLinearLayout = view.findViewById(R.id.linearLayoutImageView);
imageViewInLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition((ViewGroup)getView(), ts);
view.findViewById(R.id.longTextViewInLinearLayout).setVisibility(GONE);
}
});
View imageViewInConstraintLayout = view.findViewById(R.id.constraintLayoutImageView);
imageViewInConstraintLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition((ViewGroup)getView(), ts);
view.findViewById(R.id.longTextViewInConstraintLayout).setVisibility(GONE);
}
});
}
I have a layout that animate as below
The txt_billion is shown dynamically, with android:animateLayoutChanges="true" (Layout code below).
Notice the Hundred is jumping (actually all are jumping, but the Hundred is just more obvious). How to prevent the text from jumping?
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="8dp"
android:background="#9f9"
android:text="Hundreds" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="8dp"
android:background="#f9f"
android:text="Thousands" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="8dp"
android:background="#0ff"
android:text="Millions" />
<TextView
android:id="#+id/txt_billion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="8dp"
android:visibility="gone"
android:background="#ff0"
android:text="Billions" />
</LinearLayout>
You could get the code from https://github.com/elye/issue_horizontal_layout_animate to test out
Try to use Support Transitions instead animateLayoutChanges
First, remove android:animateLayoutChanges="true" from your XML file
After, add compile 'com.android.support:transition:25.4.0' to your app dependencies.
Then, add this line before change visibility (TransitionManager from android.support.transition package)
TransitionManager.beginDelayedTransition(parentOfAnimatedView);
For your code
public void clickMe(View view) {
TransitionManager.beginDelayedTransition((ViewGroup) billionText.getParent());
if (billionText.getVisibility() == View.GONE) {
billionText.setVisibility(View.VISIBLE);
} else {
billionText.setVisibility(View.GONE);
}
}
The problem is that animateLayoutChanges only affects subclasses of ViewGroup. TextView can't react to layout change animations, so the text jumps. There are two ways to fix it:
1) Wrap each TextView in a FrameLayout and put the weight on the FrameLayout. You'll also have to add android:animateLayoutChanges="true" to each, as well as calling getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING) on each FrameLayout. This is kind of gross layout-wise, but it will allow you to keep using the transition animations.
2) Use a ValueAnimator and animate the weight of the (dis)appearing item. The animation may be a little choppier since it needs to lay out the LinearLayout on each frame, but it should still be passable. You'd also have to solve for text reflowing on the disappearing item, maybe by animating it fading out first and then animating the weight change.
I have android layout design in which I have an AppBar layout which will contain a Toolbar and one more LinearLayout design with circle Drawable as TextView. which is off same height. So when I try to get the Toolbar or AppBar height/width it's returning only 0.
activity_main.xml:
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#color/colorPrimary"
app:layout_scrollFlags="enterAlways" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="center_horizontal"
android:background="#color/colorPrimary"
android:orientation="horizontal"
app:layout_scrollFlags="enterAlways">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="2dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="#+id/marked_questions"
style="#style/textview_summary_omr_toolbar_count"
android:background="#drawable/bg_percentage_default"
android:text="18" />
<TextView
style="#style/textview_heading_summary_omr_toolbar"
android:text="Marked" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="2dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="#+id/correct"
style="#style/textview_summary_omr_toolbar_count"
android:background="#drawable/bg_percentage_6"
android:text="18"
/>
<TextView
style="#style/textview_heading_summary_omr_toolbar"
android:text="Correct" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="2dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="#+id/wrong"
style="#style/textview_summary_omr_toolbar_count"
android:background="#drawable/bg_percentage_wrong"
android:text="18" />
<TextView
style="#style/textview_heading_summary_omr_toolbar"
android:text="Wrong" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="2dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="#+id/conflicts"
style="#style/textview_summary_omr_toolbar_count"
android:background="#drawable/bg_percentage_3"
android:text="0" />
<TextView
style="#style/textview_heading_summary_omr_toolbar"
android:text="Conflicts" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="2dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
style="#style/textview_summary_omr_toolbar_count"
android:text="Score:"
android:textColor="#android:color/white" />
<TextView
android:id="#+id/marks_scored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:text="18/30"
android:textColor="#android:color/white"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
MainActivity.java:
Here I am initializing the Toolbar and AppBar and try to print the width and height of both in logs ,which is returning me zero.
public class MainActivity extends AppCompatActivity {
private Toolbar toolbar;
private AppBarLayout app_bar;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
app_bar = (AppBarLayout) findViewById(R.id.app_bar);
toolbar = (Toolbar) findViewById(R.id.toolbar);
logToolbarLayoutParams();
}
private void logToolbarLayoutParams() {
Log.d(TAG,"Toolbar width/Height"+this.toolbar.getWidth()+"/"+this.toolbar.getHeight());
Log.d(TAG,"App_bar width/height"+this.app_bar.getWidth()+"/"+this.app_bar.getHeight());
}
I have also referred to this question in the stackoverflow.
final AppBarLayout app_bar = (AppBarLayout) findViewById(R.id.app_bar);
ViewTreeObserver vto = app_bar.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
app_bar.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.d(TAG, "Global layout");
logToolbarLayoutParams();
}
});
If I add the following code in my MainActivity it's getting me the proper height and width of Toolbar. From the stackoverflow question which I mentioned before, I came to know that, the views were not drawn on the screen, so I have to wait. But my doubt here is, the design which i have specified is not a complex one?
why its taking much time to draw on screen? am I missing something?
I have used Hierarchy Viewer to find out, what's making the child layout to load slowly. I found that I am using mulitple LinearLayout where single RelativeLayout can be used to obtain the same structure. I have changed my layout design as the following.
Still I am facing the same issue.
updated: activity_main.xml:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#color/colorPrimary"
app:layout_scrollFlags="enterAlways" />
<RelativeLayout
android:id="#+id/summary_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="center_horizontal"
android:background="#color/colorPrimary">
<TextView
android:id="#+id/marked_questions"
style="#style/textview_summary_omr_toolbar_count"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:background="#drawable/bg_percentage_default"
android:text="18" />
<TextView
android:id="#+id/marked_textview"
style="#style/textview_heading_summary_omr_toolbar"
android:layout_toEndOf="#+id/marked_questions"
android:layout_toRightOf="#+id/marked_questions"
android:text="Marked" />
<TextView
android:id="#+id/correct"
style="#style/textview_summary_omr_toolbar_count"
android:layout_toEndOf="#+id/marked_textview"
android:layout_toRightOf="#+id/marked_textview"
android:background="#drawable/bg_percentage_6"
android:text="18"
/>
<TextView
android:id="#+id/correct_textview"
style="#style/textview_heading_summary_omr_toolbar"
android:layout_toEndOf="#+id/correct"
android:layout_toRightOf="#+id/correct"
android:text="Correct" />
<TextView
android:id="#+id/wrong"
style="#style/textview_summary_omr_toolbar_count"
android:layout_toEndOf="#+id/correct_textview"
android:layout_toRightOf="#+id/correct_textview"
android:background="#drawable/bg_percentage_wrong"
android:text="18" />
<TextView
android:id="#+id/wrong_textview"
style="#style/textview_heading_summary_omr_toolbar"
android:layout_toEndOf="#+id/wrong"
android:layout_toRightOf="#+id/wrong"
android:text="Wrong" />
<TextView
android:id="#+id/conflicts"
style="#style/textview_summary_omr_toolbar_count"
android:layout_toEndOf="#+id/wrong_textview"
android:layout_toRightOf="#+id/wrong_textview"
android:background="#drawable/bg_percentage_3"
android:text="0" />
<TextView
android:id="#+id/conflicts_textview"
style="#style/textview_heading_summary_omr_toolbar"
android:layout_toEndOf="#+id/conflicts"
android:layout_toRightOf="#+id/conflicts"
android:text="Conflicts" />
<TextView
android:id="#+id/score_textview"
style="#style/textview_summary_omr_toolbar_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="#+id/conflicts_textview"
android:layout_toRightOf="#+id/conflicts_textview"
android:background="#null"
android:gravity="center"
android:text="Score:"
android:textColor="#android:color/white" />
<TextView
android:id="#+id/marks_scored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical"
android:layout_toEndOf="#+id/score_textview"
android:layout_toRightOf="#+id/score_textview"
android:gravity="center"
android:text="18/30"
android:textColor="#android:color/white"
android:textSize="18sp" />
</RelativeLayout>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
The following is the hierarchy viewer Image I am obtaining
You're thinking about this wrong. There is nothing wrong with your layout (as it relates to your question). To prove it, completely change your layout and try again
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recyclerView);
Log.d("SomeTag", "w: " + recyclerView.getWidth() + ", h: " + recyclerView.getHeight());
Log.d("SomeTag", "mw: " + recyclerView.getMeasuredWidth() + ", mh: " + recyclerView.getMeasuredHeight());
}
The output for a view with only 1 child is still
12-14 10:17:04.902 3004-3004/? D/SomeTag: w: 0, h: 0
12-14 10:17:04.902 3004-3004/? D/SomeTag: mw: 0, mh: 0
So there is nothing wrong with your layout (except you do have a misnomer. You call the method logToolbarLayoutParams, but you aren't accessing the layout params at all. You are trying to get properties on the view object before the layout pass has occurred. Maybe this is your confusion). So what you're missing is an understanding of Android's layout system, viewgroups, and views. Romain Guy had a really cool talk at one point on Devoxx about how to build a custom FlowLayout. Either the site is down or the video is pulled but here's the code in GitHub If you look at the method onLayout, you can see that a ViewGroup loops through all of it's children and calls child.layout(). Until this method is called, the view.getWidth and view.getHeight methods will return 0. And this method is called by the OS whenver it schedules it. So basically, you are trying to access the width and height immediately even before the OS scheduled a layout. Make a custom viewgroup yourself, add some break points, and give it a shot.
toolbar.getWidth() will tell you the width of the view after it has been measured and laid out.
After inflating the view its width and height will always be 0 because neither a measure() nor a layout() has occured.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = (Toolbar) findViewById(R.id.toolbar);
// check after inflation -> will be 0
logToolbarLayoutParams();
}
onGlobalLayout() reports the correct data, because—as the name suggests—both, onMeasure() and onLayout() have been called. The dimensions of the view are known. This does not mean the view has been drawn. onDraw() is the third and last step.
It also does not make any difference of how complex your layout is or which views you are using. Measuring and layout passes don't happen immediately after the inflation of the view.
To get and work with the correct dimensions of your view you have 3 options:
Use ViewTreeObserver.OnGlobalLayoutListener#onGlobalLayout() like you already suggested
Check the size to a later time, when you can be sure the view has been drawn, (e.g. upon user interaction, in an OnClickListener)
Go custom and create your own View. There you get the opportunity to handle all of onMeasure, onLayout, and onDraw yourself!
Drawing views take considerable time but we can reduce by removing nested layout and make view hierarchy flat either by using RelativeLayout or Constrain layout.
To find overdraw. Select "Show overdraw areas" in Debug GPU overdraw developer options from settings.
Study this and this important article from developer.android on optimizing layouts topic
I've just solved same problem in same case. All solutions with ViewTreeObserver.OnGlobalLayoutListener() look's dirty and not works for me. The easiest way to know width and height at current screen looks like this:
toolbar.post(new Runnable() {
#Override
public void run() {
int headerWidth = toolbar.getWidth();
int headerHeight = toolbar.getHeight();
// do whatever you want ...
}
});
view.post() will add your code in the end at view's message queue, when all sizes will already calculated. imho, that's obviously and more understandable, then OnGlobalLayoutListener().
Hope it will help you.
Anyway, this solution is just "delayed getting sizes", like a OnGlobalLayoutListener(). But this is normal behavior of system, it takes some time to draw views on the screen. Best way is not expecting calculated dimensions - instead you should coordinate all relationships between views in .xml files. Attributes like WRAP_CONTENT and MATCH_PARENT is easiest way to solve problems like this
you try to get width and height of a view before its attached to screen
you can create a custom view and wath you need in onAttach method
I am trying to set focus on a LinearLayout view when returning to an activity from another activity. The scenario:
I have one activity with a bunch of LinearLayout items (with stuff in them), some of which are text, others photos. Let's say the top most of 20 is a TextView and the rest are photos. When I leave this activity (to take a picture), activity goes to the top of the list where the TextView is - no matter what I do.
How do I force the focus back on the item that I was on before the new activity was triggered?
I have already tried this with no success:
[How Linear Layout get Focus?
I am currently trying to force the focus by remembering the previous view without success (though I can see the code being executed). The in my main activity:
#Override
public void onResume() {
super.onResume();
// See if we had a field with focus previously...
if (mPreviousView != null) {
if (mPreviousView.isFocusable()) {
mPreviousView.requestFocus();
}
}
}
...
#Override
protected void onPause() {
super.onPause();
mPreviousView = this.getCurrentFocus();
}
I am dynamically setting the focus in the LinearLayout:
private void initialize() {
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.field_photo_picker, this);
// To allow focus to be returned to control after taking photo
this.setFocusableInTouchMode(true);
this.setFocusable(true);
this.setClickable(true);
And finally the XML for the LinearLayout:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:id="#+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="normal"
android:text="" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/photoValue"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textSize="17sp"
android:textStyle="normal"
android:gravity="right|center_vertical"
android:layout_weight="1"
android:layout_marginRight="5dp"
android:text="" />
<ImageButton
android:id="#+id/photoPicker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/form_photo"
android:padding ="5dp"
android:gravity="center_vertical"
android:focusableInTouchMode="false"
android:background="?android:attr/selectableItemBackground"
android:layout_weight="0"
/>
</LinearLayout>
<View
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/grey" />
</LinearLayout>
</merge>
make your textview and other views focusable, you made this.setFocusable(true); which doesn't make sense
I've run into a very annoying problem regarding ScrollView resizing, and I'm running out of possible solutions.
I have a FragmentPager containing several different Fragments, one of which has a ScrollView.
The Fragment with the ScrollView is made up of a Spinner and the ScrollView containing a LinearLayout with several rows of other Views (such as SeekBars, Buttons, Edittexts) in it.
Depending on which option is select in the Spinner, the ScrollView shows different views. To do so, some Views have their visibility turned to Gone, while others are turned to Visible.
This works great, except for the fact that the ScrollView does not seem to resize itself properly upon choosing a different option using the Spinner.
When the ScrollView is full of Views, and therefore scrollable, if the user selects an option which shows less Viewsthan required to fill the ViewPort the ScrollView still scrolls.
When the user then chooses the old option again, the ScrollView is now unable to scroll since it took on the size required for the previous option.
When the user then chooses the SAME option again, the ScrollView suddenly is scrollable, since it is now resized to the actual size required.
What is going on here? And better yet, how can I fix this annoying problem?
My layout:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/control_scroll"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#99000000"
android:fillViewport="true" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#DD000000"
android:gravity="bottom"
android:orientation="vertical" >
<TextView
android:id="#+id/lamp_choose_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:paddingLeft="5dp"
android:text="#string/choose_lamp_text"
android:textColor="#FFFFFF"
android:textSize="14sp" />
</LinearLayout>
<Spinner
android:id="#+id/lamp_select_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="#FFFFFF" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#DD000000"
android:gravity="bottom"
android:orientation="vertical" >
<TextView
android:id="#+id/lamp_settings_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:background="#44000000"
android:paddingLeft="5dp"
android:text="#string/lamp_settings_text"
android:textColor="#FFFFFF"
android:textSize="14sp" />
</LinearLayout>
<!-- Lamp name -->
<LinearLayout
android:id="#+id/naam_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingBottom="3dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="3dp" >
<TextView
android:id="#+id/bridge_naam"
style="#style/ConfigText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="#string/config_lightname"
android:textColor="#FFFFFF"
android:textSize="16sp" />
<EditText
android:id="#+id/lamp_naam_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:background="#drawable/tasstextfield"
android:inputType="textNoSuggestions"
android:textColor="#FFFFFF" >
</EditText>
</LinearLayout>
<View
android:id="#+id/separator"
android:layout_width="fill_parent"
android:layout_height="0.5dp"
android:background="#android:color/black"
android:visibility="visible" />
<!-- Rest of the views -->
Things I already tried:
ForceLayout/requestLayout on the parent ScrollView
Invalidate the ScrollView
ForceLayout/requestLayout on the containing LinearLayout
Invalidating the LinearLayout
Invalidating all children of the ScrollView
ForceLayout/requestLayout on all children of the ScrollView
This might not be the most enjoyable solution, but it might work.
Use a handler to post a delayed message to refresh the view a second time, as if the user would choose the same option twice after having click on the refresh button.
// The code when the user wants to update the views
myButton.setOnClickListener(new OnClickListener{
#Override
public void onClick(View view) {
updateView();
Message m = Message.obtain();
m.what = UPDATE_TAG;
mHandler.sendMessageDelayed(msg, 200);
}
})
...
final int UPDATE_TAG = 1;
public void updateView() {
// Code where View.GONE and View.VISIBLE + invalidate is used.
}
Handler mHandler = new Handler {
#Override
public void handleMessage(Message input_msg) {
if(msg.what == UPDATE_TAG) {
updateView();
}
}
}
Try with this changes
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/control_scroll"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#99000000"
android:fillViewport="true" >
Quit the android:layout_weight="1"
I suppose that LinearLayout uses some kind of caching mechanism to avoid measuring childs every time. Or maybe I'm wrong.
However, you can try to use RelativeLayout instead of bunch of LinearLayout. This will solve your problem and also this will be much more efficient, since nested ViewGroups isn't good for performance.
You can go ahead and set the ScrollView height to "wrap_content" so that even if the child views change the ScrollView can adjust to the new setting