Get the ListView's width in the Array Adapter - android

I have a ListView which will be either full screen width (phone) or roughly 1/3 of the width if it's a landscape tablet. In the List it will display an imaged on a remote server.
Obviously the image can take some time to download the image, so, in the JSON data, I have an average colour of the image as hex, and the ratio of the image. What I want is, in the adapter, to fill the image with the average colour at the right size. I've got the colour part down, using this code
final ColorDrawable cd = new ColorDrawable(Color.parseColor(blog.getAverageColor()));
Picasso.with(getContext()).load(blog.getFullPath()).placeholder(cd).into(img);
However, I can't get the size to work. My original idea was to call getWidth() on the view, and times it by blog.getRatio(), then set the images height to that, and it's width to the width of the view.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = getLayoutInflater(null).inflate(r, parent, false);
}
if (view == null) {
return null;
}
int width = view.getWidth();
However, width is always 0. How can I get the width of the view? Or is this just not possible to do like this?

You can get width of listView by call parent.getWidth(). parent is a view that your view will be attached to. So your view will have same sizes.
view.getWidth() returns 0 because at this point your view isn't really added to ListView.

Related

How to get the height of recyclerview item in "onBindViewHolder"

The height of RecyclerView item is not fixed,i need to set the background image for every item,so I want to get the height of recyclerview's item to resize the Image,but the itemView.getHeight() always return 0 in onBindViewHolder.
I have try to search many questions or articles,but i still cant get a good soluation.
Short
Measure the View manually
view.measure(
View.MeasureSpec.makeMeasureSpec(recyclerViewWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
and get the height with view.getMeasuredHeight()
Background
A view will return a valid value for View.getHeight()only after it has been measured. The measuring itself will automatically happen by the system when the view is about to be displayed on screen.
When Android wants to display the layout, it will recursively call the view.layout() function for each view in the view tree. Each Parent tells its children the constraints they might have (width/height) and ask them to view.measure() themselves. As a result, the view will store the measured values BASED on the constraints in designated members (MeasuredHeight/Width). Note that at this point view.getMeasuredHeight() will hold the value while view.getHeight() will still be invalid. view.getHeight() will only return a valid value once the view has an actual height in the UI hierarchy.
Recap
So, to know the height of a view element, before it has been measured and laid out by the system, we will need to invoke the view.measure() function manually.
The measure function expects 2 parameters which derived from the view LayoutParams + the parent constraints.
In the above code sample, we are measuring the view forcing its width to be EXACTLY the width of the parent (the RecycleView), and the height is not limited.
I suggest that you define multiple layout files with the expected heights and inflate them according to some criteria in your data set.
ViewHolder onCreateViewHolder (ViewGroup parent, int viewType){
if(some condition){
//inflate layout 1
}else{
//inflate layout 2
}
or as answered here: you can get the measurements while initializing the view holder
itemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int width = itemView.getMeasuredWidth();
int height = itemView.getMeasuredHeight();
How about this:
view.post(() -> {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
using this code to get recycler view's item height:
view.getViewTreeObserver().addOnGlobalLayoutListener(new
ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//don't forget remove this listener
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//get item height here
int itemHeight = v.getHeight();
}
});
MyViewHolder.kt
With the addOnGlobalLayoutListener() method, the height value is obtained before the TextView is drawn. And then save it in a member variable.
The key is to modify the UI inside and outside the implementation of the listener so that there are no rendering problems (when the views are redrawn).
That is, you shoud use the getter inside the listener and; the setter inside and outside the listener.
companion object {
var maxHeight: Int = 0
fun create(mContext: Context): MyViewHolder {
val view = LayoutInflater.from(mContext).inflate(R.layout.item_answer, null)
updateLayout(view)
view.viewTreeObserver.addOnGlobalLayoutListener {
if (view.myTextView.height > maxHeight)
maxHeight = view.myTextView.height
updateLayout(view)
}
return MyViewHolder(mContext, view).apply {
setIsRecyclable(false)
}
}
fun updateLayout(view: View) {
if (maxHeight != 0 && view.myTextView.height != maxHeight)
view.myTextView.height = maxHeight
}
}
Source

How do I fit an exact number of ListView items on screen?

I would like the rows in a ListView to be sized so that exactly six of them can fit on the screen. For that, I would need to know how much vertical space is available to the ListView (not the whole screen). However, no measuring can be done in onCreate() since no views have been rendered yet.
If I make measurements after rendering, the ListView might be drawn and then resized, which may be distracting. What is the smartest way to establish the necessary row height before rendering the ListView?
in onCreate you can get the height of your screen and divide by 6.
Now in your getView you get the reference of the top layout for each item, suppost you have named it's id to root and i.e it's a LinearLayout.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null){ some inflate }
LinearLayout root = (LinearLayout) view.findViewById(R.id.root);
LayoutParams lp = root.getLayoutParams();
lp.height = screenHeight/6;
root.setLayoutParams(lp);
....
return view;
}
Yes, this assumes the ListView is in fullscreen.
If you have other layouts, you will have to get those height into account.
Then your height will be: int heightForEachItem = (screenHeight - otherlayoutsHeightTogether) / 6;
Turns out that the earliest you can measure a ListView is in the onGlobalLayout() callback.
Here is my example.
params = new AbsListView.LayoutParams(-1,-1);
listview.getViewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
#Override
public void onGlobalLayout(){ //this is called just before rendering
params.height = listview.getHeight()/6; // this is what I was looking for
listview.getViewTreeObserver.removeOnGlobalLayoutListener(this); // this is called very often
}
adapter = new ArrayAdapter<...>(int position, ...){
#Override
public View getView(...){
LinearLayout item = new LinearLayout(context);
item.setLayoutParams(params);
// add text, images etc with getItem(position) and item.addView(View)
return item;
}
}
listview.setAdapter(adapter);

Setting ImageView width and height before drawing in a ListView adapter

I have an adapter to a ListView is a list of ImageViews. I am using a stretch to make the image fil the imageview so I can take smaller images and make them larger on the screen, however the ImageView normally just uses wrap_content and this is an issue because the images just show up as their normal width and height. Is there any way I can set the height and width of a view before drawing it because as in this case I do not have control over the view after it has been drawn. Here is my aapter method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
String currentImage = getItem(position);
ScaleType scaleType = ScaleType.FIT_CENTER;
float screenWidth = parent.getResources().getDisplayMetrics().widthPixels;
if (convertView == null) {
convertView = new ImageView(parent.getContext());
}
// WHAT I WOULD LIKE TO BE ABLE TO DO, but this returns null pointer exception
// convertView.getLayoutParams().width = (int) screenWidth;
// convertView.getLayoutParams().height = (int) ((float)(screenWidth/currentImage.getWidth())*currentImage.getHeight());
((ImageView) convertView).setScaleType(scaleType);
((ImageView) convertView).setImageBitmap(MainActivity.cache.getBitmap(currentImage));
return convertView;
}
How about something like someView.setHeight() and someView.setWidth()? Or someView.setLayoutParams()? You could add either of these to the overridden getView() callback and it should take care of your problem.
You could also Create a Custom View and override something like getMeasuredWidthAndState(). (I think that's one of the right methods, but I'm not one hundred percent sure.) You could create a width class variable and a height class variable that all instances of your custom ImageView would use. However, that might be a bit much if you just want to set the layout width and height though.

A ListView where a specific row can always be scrolled to the top?

I want to be able to take a ListView and have a specific row be scrollable to the top of that Listview's bounds, even if the row is near the end and normally wouldn't be able to scroll that high in a normal android ListView (similar to how twitter works when you drill into a specific tweet and that tweet is always scrollable to the top even when there's nothing underneath it.)
Is there any way I can accomplish this task easily? I've tried measuring the row i want to scroll to the top and applying bottom padding to account for the extra space it would need, but that yields odd results (i presume because changing padding and such during the measure pass of a view is ill advised). Doing so before the measure pass doesn't work since the measured height of the cell in question (and any cells after it) hasn't happened yet.
Looks like you the setSelectionFromTop method of listview.
mListView.setSelectionFromTop(listItemIndex, 0);
I figured it out; its a bit complex but it seems to work mostly:
public int usedHeightForAndAfterDesiredRow() {
int totalHeight = 0;
for (int index = 0; index < rowHeights.size(); index++) {
int height = rowHeights.get(rowHeights.keyAt(index));
totalHeight += height;
}
return totalHeight;
}
#Override
public View getView(int position, View convertView, final ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if (measuringLayout.getLayoutParams() == null) {
measuringLayout.setLayoutParams(new AbsListView.LayoutParams(parent.getWidth(), parent.getHeight()));
}
// measure the row ahead of time so that we know how much space will need to be added at the end
if (position >= mainRowPosition && position < getCount()-1 && rowHeights.indexOfKey(position) < 0) {
measuringLayout.addView(view, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
measuringLayout.measure(MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);
rowHeights.put(position, view.getMeasuredHeight());
measuringLayout.removeAllViews();
view.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
if (position == getCount()-1 && view.getLayoutParams().height == 0) {
// we know how much height the prior rows take, so calculate the last row with that.
int height = usedHeightForAndAfterDesiredRow();
height = Math.max(0, parent.getHeight() - height);
view.getLayoutParams().height = height;
}
return view;
}
This is in my adapter. It's a subclass of a merge adapter, but you can just put it in your code and substitute the super call with however you generate your rows.
the first if statement in getView() sets the layout params of a frame layout member var that is only intended for measuring, it has no parent view.
the second if statement calculates all the row heights for rows including and after the position of the row that I care about scrolling to the top. rowHeights is a SparseIntArray.
the last if statement assumes that there is one extra view with layout params already set at the bottom of the list of views whose sole intention is to be transparent and expand at will. the usedHeightForAndAfterDesiredRow call adds up all the precalculated heights which is subtracted from the parent view's height (with a min of 0 so we don't get negative heights). this ends up creating a view on the bottom that expands at will based on the heights of the other items, so a specific row can always scroll to the top of the list regardless of where it is in the list.

Gallery with setScaleX/Y on children

I have a gallery feeded by custom adapter using custom views as elements. I need these elements to be scaled by 70% by default. The problem is that gallery behaves like they are at 100% size with 30% transparent padding = spacing between elements are just big. For better understanding I attached two images with elements scaled to 100% and 70%.
Elements scaled to 100%
Elements scaled to 70%
I cannot hardcode the setSpacing as this would behave weird on different resolutions. I tried setSpacing(0) with no luck. How can I achieve that gallery would behave like the elements are small (70%) and not the original size?
I'm scaling the elements by adding setScaleX/Y to the constructor of custom element MagazineCell which extends RelativeLayout:
public MagazineCell(Context context) {
super(context);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = (RelativeLayout) inflater.inflate(R.layout.mag_big, null);
addView(layout);
this.setScaleX(0.7f);
this.setScaleY(0.7f);
}
I also tried to set the scale in drawChild() of the gallery with no luck. In adapter I'm simply using this class for gallery elements:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
MagazineCell cell;
position = getPosition(position);
if (null == convertView || convertView.getClass() != MagazineCell.class) {
cell = new MagazineCell(context);
} else {
cell = (MagazineCell) convertView;
}
return cell;
}
Gallery has no special code. I'm using SDK 11 on Acer Iconia TAB A500 running Android 3.1.
Thanks for any hints or comments.
Looks like there's no way to "trick" Gallery to measure scaled cells with their scaled width. So I just used negative spacing - setSpacing(-100); There may be a way via overriding onMeasure method on MagazineCell class and calculate scaled width. But I didn't try this.
after
this.setScaleX(0.7f);
this.setScaleY(0.7f);
add
this.setPivotX(0);
this.setPivotY(0);

Categories

Resources