I want to make a GridView that contains 75dp width and 75dp height imageView. And also i want this square fill the screen size, with no space in between. Right now i am just using 500 count. So its randomly printing 500 square images. Its not filling up the space. Please check the image below for tablet version.
Here is my Adapter:
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return 500;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some
// attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(75, 75));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(0, 0, 0, 0);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(R.drawable.sample_0);
return imageView;
}
}
In getCount you could divide the area of the screen by the area of the squares you're filling it with.
public int getCount() {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int area = size.x * size.y;
// TODO: you may want to pass in the square dimensions or make them constants
return area / (75 * 75);
}
Of course, this isn't right, since it uses the display and not the parent ViewGroup. Instead of trying to do this all in the adapter, have the constructor for this take in the dimensions of the parent and do the math there, saving an instance variable with the count:
private int numBoxes = 0;
private static final SQUARE_SIZE = 75;
public ImageAdapter(Context c, ViewGroup parent) {
mContext = c;
numBoxes = (parent.getWidth() * parent.getHeight()) / (SQUARE_SIZE * SQUARE_SIZE);
}
public int getCount() {
return numBoxes;
}
Expanding on the answer by Nick White, you need to make sure the parent layout has been laid out before you can get the height and width. If the parent width and/or height are "wrap_content" or "match_parent", the getHeight() and getWidth() will return zero in the adapter's constructor, if the adapter is being created in the Activity onCreate().
This answer gives a way to listen for onLayout events and handle sizing. I did try to do something similar, but I found the results in my case were less than perfect, as the grid would draw then redraw after I applied the size changes. So far the best results I've had with grid sizing is to use qualified resource values to size the image views inside the grid. The resource size value is then set according to the screen density or size by creating various values in values-xxx resource folders.
In my case, I was altering the size of the cell images to make a certain number fit the screen by applying something like:
Integer size = getResources().getInteger(R.int.image_size));
imageView.setLayoutParams(new GridView.LayoutParams(size, size));
It should be possible to do something similar to calculate the number of columns for the screen size.
Related
I have a RecyclerView which has a staggeredGridLayoutManager as layout manager. My layout stands as having 2 spans(cols), which items inside may have different heights.
Inflated items has a ImageView and some other views inside a LinearLayout container.
I want to save Inflated(or should I say binded?) View's size(height and width) after the view's image is fully loaded. Because this operation makes me know how much width and height the LinearLayout occupy at final-after the image is placed in the layout-.
After scrolling, this container may be recycled and binded again. What I want to achieve is to savebinded layout's size immediately after it is binded, according to the height and width values previously calculated because this makes recyclerView's item positions more stable. They are less likely move around.
I have mWidth and mHeight members in my ViewHolder, which basically store these values. However, I lost syncronisation between item position in adapter and corresponding ViewHolder. For example I calculate height of 8th item as 380px when it first become visible, which is correct. After recycling and binding 8th position again, my view's height retrieved as 300 px, which is incorrect.
Code:
BasicActivity is derived from Activity..
public ItemsRVAdapter(BasicActivity activity, JSONArray items){
this.items = items;
this.activity = activity;
this.itemControl = new Items(activity);
}
OnCreate:
#Override
public ItemListViewHolders onCreateViewHolder(ViewGroup viewGroup, int i) {
View layoutView =activity.getLayoutInflater().inflate(R.layout.list_element_items, viewGroup, false);
ItemListViewHolders rcv = new ItemListViewHolders(layoutView);
return rcv;
}
OnViewAttachedToWindow (I tried the same code here in different places, like onViewRecycled but I don't know this method is the most right place to calculete the size)
#Override
public void onViewAttachedToWindow(ItemListViewHolders holder)
{
holder.layoutCapsule.measure(LinearLayout.MeasureSpec.makeMeasureSpec(0, LinearLayout.MeasureSpec.UNSPECIFIED), LinearLayout.MeasureSpec.makeMeasureSpec(0, LinearLayout.MeasureSpec.UNSPECIFIED));
if(holder.image.getDrawable() != null){
holder.height = holder.layoutCapsule.getHeight();
holder.width = holder.layoutCapsule.getWidth();
}else{
holder.height = 0;
holder.width = 0;
}
}
onBindViewHolder: Only relevant part. Here I paired position value and my array's member index
#Override
public void onBindViewHolder(ItemListViewHolders holder, int position) {
try {
//JSONObject item = items.getJSONObject(holder.getAdapterPosition());
JSONObject item = items.getJSONObject(position);
holder.image.setImageDrawable(null);
ViewGroup viewGroup = holder.layoutCapsule; //Main Container
...
}
}
I recommend looking for a different approach to resolve your problem with the items moving around not depending on View sizes, but if you want to proceed this way this is my proposed solution:
Don't depend or save the size values on the holder as this gets recycled, you will need to create an object "descriptor" with the values (width and height) for each position and save them on a HashMap or something like that, save the values as you are doing it already, i understand on "onViewAttachedToWindow".
class Descriptor(){
int width;
int height;
void setWidth(int width){
this.width = width;
}
int getWidth(){
return width;
}
void setHeight(int height){
this.height = height;
}
int getHeight(){
return height;
}
Initialize array on constructor:
descriptors = new HashMap<Integer, Descriptor>();
in onBindViewHolder save the position on a view tag to use it on OnViewAttachedToWindow
public void onBindViewHolder(ItemListViewHolders holder, int position) {
....
holder.image.setTag(position);
...
}
populate values on onViewAttachedToWindow
public void onViewAttachedToWindow(ItemListViewHolders holder){
...
int position = (Integer)holder.image.getTag();
Descriptor d = descriptors.get(position);
if(d == null){
d = new Descriptor();
descriptors.put(position, d);
}
d.setWidth(holder.layoutCapsule.getWidth());
d.setHeight(holder.layoutCapsule.getHeight());
...
}
Then use the size data on the descriptor on the method you need getting it by position, you will be creating descriptors as the user is scrolling down, also this works on the asumption that the data maintains the same position during the life of the adapter.
I'm using RecyclerView as horizontal list to show my images.
If I scroll to the fifth picture, the first two or three are recycled and ViewHolder loses its width. If I scroll back to the first image, the images are loaded again and that leads to jumps while scrolling.
Here is R.layout.fragment_details_view_img_item
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/details_view_img_item"
android:background="#color/red"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</ImageView>
My ViewHolder and Adapter:
private class ViewHolder extends RecyclerView.ViewHolder {
ImageView img;
public ViewHolder(ImageView imgV){
super(imgV);
img = imgV;
}
}
private class ImageListAdapter extends RecyclerView.Adapter<ViewHolder> {
[...]
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
View v = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_details_view_img_item, parent, false);
v.setOnClickListener(listener);
logDebug("onCreateViewHolder");
return new ViewHolder((ImageView) v);
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
logDebug("onBindViewHolder");
ImageItem item = data.get(i);
if (item != null) {
ImageView imgView = viewHolder.img;
imgView.setTag(item);
String imgurl = ImageUtil.imgUrlForAvailableHeightInPX(item, parentHeight);
ImageLoader.instance().loadASYNC(imgurl, imgView);
}
}
#Override
public void onViewRecycled(ViewHolder holder) {
super.onViewRecycled(holder);
logDebug("onViewRecycled: " + holder.img.getTag());
}
}
So how can I keep ViewHolder's width?
Before I start to approach this problem, one thing needs to be clear. The ViewHolder doesn't have any width, the ImageView it "holds" does have width, and that's what you're trying to control.
Now, considering this, your issue is (after making certain assumptions from looking at your code) that you need to know the width of a certain image when it is at a certain given height and while maintaining aspect ratio - before it arrives from the server.
This is a tricky one.
One option would be to preload all your images. This, however, is very costly with memory and could lead to memory crashes.
A better option would be to load all the images' details, without actually downloading the images' pixels. Then, you'll need to remember the aspect ratio of all the images in some cache, and set the dimension of the image you're loading prior to actually downloading the image contents.
To download an image's dimensions without downloading the image itself, you should use something like this:
public float getImageAspectRatio(InputStream inputStreamFromServer)
{
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStreamFromServer, null, decodeOptions);
final float imageDesiredWidth = decodeOptions.outWidth;
final float imageDesiredHeight = decodeOptions.outHeight;
return imageDesiredWidth / imageDesiredHeight;
}
Once you have this method, you'll need to preload all your images using this function:
private float[] mAspectRatios;
public void decodeAllAspectRatios(List<String> imageUrls)
{
mAspectRatios = new float[imageUrls.size()];
InputStream inputStream;
int index = 0;
for (String url : imageUrls)
{
// Get the input stream from the image url using whatever method you use.
mAspectRatios[index] = getImageAspectRatio(inputStream);
index++;
}
}
Important: Your RecyclerView should not begin working until this method finished working.
Once you have preloaded all your aspect ratios, we go back to your ViewHolder:
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
logDebug("onBindViewHolder");
ImageItem item = data.get(i);
if (item != null) {
ImageView imgView = viewHolder.img;
// set the layout params of the image, making it fit the correct size prior to loading the bitmap.
imgView.setLayoutParams(new LayoutParams(parentHeight * mAspectRatios[i], parentHeight));
imgView.setTag(item);
String imgurl = ImageUtil.imgUrlForAvailableHeightInPX(item, parentHeight);
ImageLoader.instance().loadASYNC(imgurl, imgView);
}
}
So long as you want your RecyclerView to display varying width images depending on their aspect ratio, I believe this is your best option.
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.
I have created a basic RelativeLayout in my XML file. In my code, I want to dynamically create several ImageViews and place them at different locations within the RelativeLayout. Everything I've tried (ImageView.setX(), ImageView.setTranslationX(), ImageView.setPadding()) either says I need a higher API level (11+) or causes the ImageView to not appear.
If I do not try to do anything with the location of the ImageView, then the image does appear on the screen in the (0,0) position.
This simple app will layout 15 icons in three rows dynamically using RelativeLayout. There is no reason to use AbsoluteLayout - it is also deprecated.
The main activity.
public class MainActivity extends Activity {
private int mWidth;
private int mTile;
private int mColMax = 5;
private Context mContext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
// the screen width is need to work out the tile size
mWidth = mContext.getResources().getDisplayMetrics().widthPixels;
// how wide (and high) each icon will be to fit the screen.
mTile = (mWidth / mColMax);
setContentView(R.layout.activity_main);
// layout the icons
initUI();
}
/**
* Layout 15 icon images in three rows dynamically.
*/
private void initUI() {
// this is the layout from the XML
ViewGroup layout = (ViewGroup) findViewById(R.id.main_layout);
ImageView iv;
RelativeLayout.LayoutParams params;
int i = 0;
int row = 0;
int col = 0;
do {
params = new RelativeLayout.LayoutParams(mTile,mTile);
params.setMargins((col * mTile), (row * mTile), 0, 0);
iv = new ImageView(mContext);
iv.setAdjustViewBounds(true);
iv.setScaleType(ScaleType.FIT_CENTER);
iv.setImageResource(R.drawable.ic_launcher);
iv.setLayoutParams(params);
layout.addView(iv);
if (col == mColMax) {
row++;
col = 0;
} else {
col++;
}
} while (++i <= 16);
}
}
And the layout XML.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
RelativeLayouts are used to place items in relationship to other items. YOu don't use them to place layouts at specific positions like a setX. If you want to place the new item relative to existing items, look at RelativeLayout.LayoutParams- you can set layout_alignXXX and layout_toXXXOf type parameters through them.
If you need an exact pixel position, use the deprecated AbsoluteLayout. Just be aware its going to look ugly as heck on any device with a different aspect ratio or size screen without a ton of work.
I have a gallery in landscape mode with a lot of images with an imageadapter. I want that the images stretch to the whole screen Horizontally, and keep the aspect ratio and stretch as much as needed vertically..
I tried to use FIT_XY, it doesnt fit Horizontally, but it doesnt keep the ratio vertically ( the images become crushed)
How can I do this? Here is the custom image adapter:
public class ImageAdapter extends BaseAdapter {
int mGalleryItemBackground;
int counter = 0;
private final Context mContext;
public String[] mImageIds;
public ImageAdapter(Context c) {
mContext = c;
TypedArray a = c.obtainStyledAttributes(R.styleable.Gallery1);
mGalleryItemBackground = a.getResourceId(R.styleable.Gallery1_android_galleryItemBackground, 0);
a.recycle();
}
public void insert(String string) {
mImageIds[counter] = string;
counter++;
}
#Override
public int getCount() {
return mImageIds.length;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView i = new ImageView(mContext);
i.setImageBitmap(BitmapFactory.decodeFile(mImageIds[position]));
i.setLayoutParams(new Gallery.LayoutParams(WindowManager.LayoutParams.FILL_PARENT, WindowManager.LayoutParams.FILL_PARENT));
i.setScaleType(ImageView.ScaleType.MATRIX);
i.setBackgroundResource(mGalleryItemBackground);
return i;
}
}
when I do this in android I'm using a fit scaling (fitCenter, fitStart, fitEnd) together with adjust view bounds the xml attributes would be e.g.
android:scaleType="fitCenter" android:adjustViewBounds="true"
You can compute the pixels for width and height and send them to setLayoutParams:
int width = getWindowManager().getDefaultDisplay().getWidth();
int height = width * bitmapHeight / bitmapWidth;
imageView.setLayoutParams(new Gallery.LayoutParams(width, height));
imageView.setPadding(6, 0, 6, 0); // set this to what you like
// remove this line:
//imageView.setScaleType(ImageView.ScaleType.MATRIX);
Setting the scale type won’t do anything (except FitXY will stretch horizontally to the bounds, but leave the height as something else, as you mentioned). Also FILL_PARENT does not seem to work as expected. The other layouts seem to affect the bitmap size (or in my case, Drawable).
In your layout xml file, you might have to set fill_parent:
android:layout_width="fill_parent"
android:layout_height="fill_parent"
This worked for me downloading and streaming into a Drawable, so I think it will work for your bitmap.