I have a Cardview with height WRAP_CONTENT . Content of Cardview is to be populated after network call . And based on the height of Cardview I have to set background in my fragment . So I tried using ViewTreeObserver and Post method but I am still getting value as 0.
cardTierInfo.post {
cardTierInfoHeight = cardTierInfo.height
setLayoutParams(cardTierInfoHeight)
}
Call is being made in OncreateView.
If you using kotlin extends core-ktx it is easy to get actual height by doOnLayout
cardTierInfo.doOnLayout {
cardTierInfoHeight = cardTierInfo.height
setLayoutParams(cardTierInfoHeight)
}
Related
How to set the child view's width using data binding. The value to set is dynamic and it depends on the width of the parent layout.
item_bar.xml
<LinearLayout
android:id="#+id/barLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="#{model.usedBarWidthPercentage}"
android:layout_height="4dp"/>
</LinearLayout>
The model can give me a percentage. Ex, if the percentage is 40%, it means the width of the textview should be 40% of the parent layout.
I know the idea of using data binding adapters, but dont know how to do it with the parent layout's width.
You can create a class file, example MyBingding.class
#BindingAdapter({ "bindWidth" })
public static void bindWidth(TextView textView, double perc) {
//You can do something by java code here
textView.xxxxxxxx;
}
Then use bindWidth method in XML:
<TextView
app:bindWidth="#{model.usedBarWidthPercentage}"
android:layout_width="wrap_content"
android:layout_height="4dp"/>
Make sure the usedBarWidthPercentage's data type is same as bindWidth method's perc.
I'm afraid you will be disappointed on this one iori24. I tried for qhite a while to figure out how to accomplish this, but some variables can be done post draw and some have to be done pre draw. Binding is meant to be for post draw type of values. layout_width and layout_height are pre draw values that need to be set ahead of time.
Unfortunately there is no current way to do this. I found many articles on it and can confirm this statement. However, I managed to find ways to manipulate my UI with various match parents or weights to accomplish what I needed or adjust in code.
I would love if they brought data binding to layout_width and height, and I think they will eventually, but for now you will just have to get more creative with your design.
If you would like you could provide your full sample code for someone to modify and hand back with potential fixes, but I'm guessing you will need a code option here and not an XML option. Now namezhouyu posted a binding adapter option. This is a postdraw that will take the textview and potentially modify it's value after it has been drawn. This could work, but you may see strange behavior as the original size would be wrap_content and then each subsequent change would cause it to jump to correct size. But I do like his work around, it is a clever solution.
So if you are bound and determined to do it through binding, then I would follow namez houyu's option.
What I end up doing is follow #Raghunandan's suggestion
class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(model: Model) {
Logger.d("START")
binding.model = model
val treeObserver = binding.viewLayout.viewTreeObserver
if (treeObserver.isAlive) {
treeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.viewLayout.viewTreeObserver.removeOnGlobalLayoutListener(this) // need to call viewTreeObserver again
val maxValue = binding.viewLayout.width
val usePerc = binding.model.getUsedBarWidthPercentage()
Logger.d("maxValue=$maxValue, usePerc=$usePerc")
binding.view.layoutParams.width = (maxValue * usePerc).toInt()
}
})
}
}
}
I have an android app that I have decided to rewrite, one of the reasons for the rewrite is because I could have 10+ TextViews with text set based on a variable in a class e.g.:
MyClass myClass = new MyClass();
myClass.myNumber = 5; // inside MyClass - public int myNumber;
LinearLayout mainLayout = (LinearLayout) view.findViewById(R.id.mainLayout);
TextView myTextView = new TextView(getActivity()); //In a fragment
myTextView.setText(String.format("myNumber currently has a value of %d", myClass.myNumber));
mainLayout.addView(myTextView);
return view;
Up until now I have been using .setOnClickListener on the buttons/views that change myNumber, to set the text of the view again when the value of myNumber changes, which then calls .invalidate() to redraw the TextView, this has worked fine, but I use this method very heavily and code is getting quite repetitive and changing one integer can affect quite a lot of views (all of which use it differently - such as different wording, or a calculation (e.g. myNumber * 2)). I guess it's because it's made an immutable string in the TextView.
I have tried to create a custom TextView that implements Observer (making MyClass extend Observable) and in the update method I can get it to invalidate itself for the refresh, but it still has the same text. I have also tried creating single element arrays, in an attempt to pass the reference not the value in the hope that when it is changed and then the view is invalidated it will pick up the new value but the text still ends up remaining the same.
How can I get a TextView that will auto update when the value of myNumber has changed? Some sort of binding?
Bindroid works perfectly for this, just a note for users, using fragments the sample application is using this from an Activity so the bind method using Activity is called, so in the fragment I was using getActivity() which caused it to not work properly, digging around in the library I found a bind method that takes a View and passed in my view which gets inflated in the fragment and it works great!!! This is super easy to integrate btw it was just me not getting it!
Is there any way to simulate a click on a RecyclerView item with Robolectric?
So far, I have tried getting the View at the first visible position of the RecyclerView, but that is always null. It's getChildCount() keeps returning 0, and findViewHolderForPosition is always null. The adapter returns a non-0 number from getItemCount() (there are definitely items in the adapter).
I'm using Robolectric 2.4 SNAPSHOT.
Seems like the issue was that RecyclerView needs to be measured and layed out manually in Robolectric. Calling this solves the problem:
recyclerView.measure(0, 0);
recyclerView.layout(0, 0, 100, 10000);
With Robolectric 3 you can use visible():
ActivityController<MyActivity> activityController = Robolectric.buildActivity(MyActivityclass);
activityController.create().start().visible();
ShadowActivity myActivityShadow = shadowOf(activityController.get());
RecyclerView currentRecyclerView = ((RecyclerView) myActivityShadow.findViewById(R.id.myrecyclerid));
currentRecyclerView.getChildAt(0).performClick();
This eliminates the need to trigger the measurement of the view by hand.
Expanding on Marco Hertwig's answer:
You need to add the recyclerView to an activity so that its layout methods are called as expected. You could call them manually, (like in Elizer's answer) but you would have to manage the state yourself. Also, this would not be simulating an actual use-case.
Code:
#Before
public void setup() {
ActivityController<Activity> activityController =
Robolectric.buildActivity(Activity.class); // setup a default Activity
Activity activity = activityController.get();
/*
Setup the recyclerView (create it, add the adapter, add a LayoutManager, etc.)
...
*/
// set the recyclerView object as the only view in the activity
activity.setContentView(recyclerView);
// start the activity
activityController.create().start().visible();
}
Now you don't need to worry about calling layout and measure everytime your recyclerView is updated (by adding/removing items from the adapter, for example).
Just invoke
Robolectric.flushForegroundThreadScheduler()
before performClick() to ensure that all ui operations (including measure and layout phases of recycler view after populating with the dataset) are finished
I have a RecyclerView with a bunch of custom views which may change height after a while, because they contain ImageViews which load their image asynchronously. The RecyclerView does not pick up on this layout change, although I call forceLayout on the ImageView, and the RecyclerView is initialized with setHasFixedSize(false). All container-parents of the ImageView have set android:layout_height="wrap_content".
How can I make the RecyclerView update its layout? With good'ol ListView this was not a problem.
You can use the notifyDataSetChanged in the Adapter for the RecyclerView if all your images change at the same time or notifyItemChanged(int position) if only one of the items has changed. This will tell the RecyclerView to rebind the particular View.
Google has finally fixed this in support library v23.2. Issue is fixed by updating this line in build.gradle after updating your support repository with the SDK Manager:
compile 'com.android.support:recyclerview-v7:23.2.0'
Call the notifyDataSetChanged() or notifyItemChanged(int position) methods of the RecyclerView.Adapter from the MAIN THREAD. Calling it from an AsyncTask may result in the main thread not updating the ImageViews. Its most convenient to have a callback method in the activity/fragment work as a listener, and have it called by the AsyncTask in onPostExecute()
int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);
mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);
This code is worked for me.
I had the same problem, I just set a fix width and height for the ImageView. Try that and see where you get. You can also use a lib for this, I use Square Picasso and I got it working with RecyclerView.
I have a fragment and I need to measure location/width/height of its views on screen and pass to some other class.
So what I have is a function which does it, something like this :
private void measureTest(){
v = ourView.findViewById(R.id.someTextField);
v.getLocationOnScreen(loc);
int w = v.getWidth();
...
SomeClass.passLocation(loc,w);
...
The problem is that the location/width/height of views is not ready within fragment lifecycle.
So if I run that function within these lifecycle methods :
onCreateView
onViewCreated
onStart
onResume
I either get wrong location and width/height measurments or 0 values.
The only solution I found is to add a GlobalLayoutListener like this to mainView
mainView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
if(alreadyMeasured)
mainView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
else
measureTest();
}
});
this gets the job done.. but its just Yack! IMO.
Is there a better way of doing this? seems like such a basic thing to do
inside onActivityCreated of your fragment retrieve the currentView (with getView()) and post a runnable to its queue. Inside the runnable invoke measureTest()
There is no better way. That code isn't that bad! It's fired as soon as the view is layed out (my terminology might be a bit weird there) which happens right after measuring. That is how it is done in the BitmapFun sample (see ImageGridFragment, line 120) in Google's Android docs. There is a comment on that particular piece of code stating:
// This listener is used to get the final width of the GridView and then calculate the
// number of columns and the width of each column. The width of each column is variable
// as the GridView has stretchMode=columnWidth. The column width is used to set the height
// of each view so we get nice square thumbnails.