So I wanted my app to be able to display images efficiently in a listview to prevent OOM errors. I tried implementing this by converting the images to bitmaps and then set this as a background in a framelayout and then used this xml file as the layout of my listview. However, it does not display anything.
This is the adapter class.
public class CustomAdapter extends BaseAdapter {
String [] result;
Context context;
int [] imageId;
String [] peopleId;
private static LayoutInflater inflater=null;
public CustomAdapter(MyMainActivity mainActivity, String[] prgmNameList, int[] prgmImages, String[] peopleImages) {
// TODO Auto-generated constructor stub
result=prgmNameList;
context= mainActivity;
imageId=prgmImages;
peopleId=peopleImages;
inflater = ( LayoutInflater )context.
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return result.length;
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
public class Holder
{
FrameLayout frame;
ImageView pic;
TextView tv;
TextView imgPeople;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
Holder holder=new Holder();
View rowView;
rowView = inflater.inflate(R.layout.program_list_upcoming, null);
rowView.setRotation(5);
holder.frame=(FrameLayout) rowView.findViewById(R.id.frame);
holder.pic = (ImageView) rowView.findViewById(R.id.frameBackPic);
holder.imgPeople=(TextView) rowView.findViewById(R.id.peopleImage);
holder.tv=(TextView) rowView.findViewById(R.id.textView1);
holder.imgPeople.setText(peopleId[position]);
holder.frame =(FrameLayout) rowView.findViewById(R.id.frame);
holder.tv.setText(result[position]);
Bitmap bitmap = decodeSampledBitmapFromResource(Resources.getSystem(), imageId[position],400, 130);
BitmapDrawable myDrawable = new BitmapDrawable(this.context.getResources(), bitmap);
holder.pic.setImageBitmap(bitmap);
holder.pic.requestLayout();
rowView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(context, "You Clicked " + result[position], Toast.LENGTH_LONG).show();
}
});
return rowView;
}
//This is to convert the imageIDs into a array of small bitmaps
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
//this adjusts the size of the bitmap as its needed accordign to the layout dimentsions
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = (int)(height / 1.5);
final int halfWidth = (int)(width / 1.5);
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
This is the xml file associated with it
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#000000"
android:rotation="5">
<!-- First frame layout is to have the picture underneath everything-->
<FrameLayout
android:id="#+id/frame"
android:layout_width="fill_parent"
android:layout_height="130dp"
android:background="#a8000000">
<ImageView
android:id="#+id/frameBackPic"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitXY" />
<!-- First frame layout is to have the bar-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="25dp"
android:layout_gravity="fill"
android:layout_marginTop="83dp"
android:background="#b8000000">
<LinearLayout
android:id="#+id/buttonPanel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:orientation="horizontal"
android:weightSum="14">
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_centerHorizontal="true"
android:layout_gravity="left"
android:layout_weight="10"
android:paddingLeft="10dp"
android:textColor="#d1d1d1"
android:textSize="18dp" />
<TextView
android:id="#+id/distance"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="right"
android:layout_weight="1"
android:paddingLeft="10dp" />
<TextView
android:id="#+id/peopleImage"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_gravity="right"
android:layout_weight="1"
android:paddingLeft="10dp" />
</LinearLayout>
</FrameLayout>
<!-- Second frame layout is to have the translucent bar acr0ss-->
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#a8000000">
<TextView
android:id="#+id/description"
android:layout_width="wrap_content"
android:layout_height="17dp"
android:layout_below="#+id/buttonPanel"
android:layout_gravity="center_vertical"
android:layout_marginBottom="5dp"
android:background="#7e000000"
android:paddingLeft="15dp" />
</FrameLayout>
</FrameLayout>
Any insights on how to fix it would be greatly appreciated!
You have multiple problems here.
1)You aren't using listview correct. Your getView should reuse convertView if not null and only create a new view if it is null. You aren't. This makes your code totally inefficient, it isn't recycling at all.
2)Your decode function isn't decoding a bitmap. Its only decoding the size of the bitmap- inJustDecodeBounds is set to true. So you will always return null.
3)You aren't saving the bitmaps in any form of cache. This means you will decode the bitmap each time getView is called, which will be once per row on every scroll event. You're actually doing worse than not trying to be efficient at all.
4)You're doing this with resources. Resources are already inflated into full bitmaps at startup. This kind of optimization, when done correctly, only applies to image files.
5)You're sticking a bitmap in a drawable from resources. This is done for you at startup.
If you're using bitmap files or bitmaps downloaded from the net, this is a needed optimization. With what you're doing, you're actually lowering performance and hurting your app.
Related
I have a fragment that displays some images on 30% of the screen. In case when image.height is bigger than image.width, I wanted to rotate the image 90 degrees. However, after that operation, the image does not fully fill the fragment - "new" width after rotation matches previous height, so the image occupies only some part in the middle of the fragment. How can I make it a matching fragment? What I'm doing wrong or missing?
This is how it looks like without rotation:
This is after rotation:
And that's what I want to have:
Here is the code:
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setImageFittedToDisplay(view);
}
public void setImageFittedToDisplay(View view) {
int height = getDisplayMetrics().heightPixels;
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = ((Double) (height * 0.3)).intValue();
view.setLayoutParams(params);
ImageView image = view.findViewById(R.id.picture);
image.setDrawingCacheEnabled(true);
int w = image.getDrawable().getIntrinsicWidth();
int h = image.getDrawable().getIntrinsicHeight();
if(h > w) {
image.setRotation(90f);
}
ViewGroup.LayoutParams imageParams = image.getLayoutParams();
imageParams.height = ActionBar.LayoutParams.WRAP_CONTENT;
imageParams.width = ActionBar.LayoutParams.WRAP_CONTENT;
image.setLayoutParams(imageParams);
}
and here is .xml of the fragment:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/APOD_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="#+id/picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/cool_pic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/separator"
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="#color/colorSeparator"
app:layout_constraintBottom_toBottomOf="#+id/picture"
app:layout_constraintTop_toBottomOf="#+id/picture" />
</androidx.constraintlayout.widget.ConstraintLayout>
My requirements: create "incoming bubble" with width by content and max width 90%.
I have this markup:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1.0"
tools:background="#color/white_smoke">
<LinearLayout
android:id="#+id/flBubble"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:background="#drawable/bubble_in"
android:layout_weight="0.9">
<ImageView
android:id="#+id/ivSay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="#string/default_content_description"
android:padding="8dp"
android:src="#drawable/ic_play_circle_outline_black_24dp"
android:tint="#color/primary"/>
<TextView
android:id="#+id/tvValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"
android:textColor="#color/black"
android:textSize="16sp"
tools:text="I would like to go to an Italian restaurant"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0.1"/>
</LinearLayout>
Sometimes I get the following result:
But I expect the following result (it's falsely encouraging screenshot from Android Studio preview):
How can I prevent breaking word restaraunt by letters?
UPDATE
Although I use minSdk=15 I tried to use breakStrategy and I haven't get expected result.
android:breakStrategy="simple":
android:breakStrategy="balanced":
I found a related question: Force next word to a new line if the word is too long for the textview, but I didn't undestand how can I get maximum available width for TextView with layout_width="wrap_content?
It would be great if I could override the TextView.setText and place line breaks there if needed.
OMG, there were in my string!
value.replaceAll("\\s", " ");
Thank you all!
Use MaxWidth property for textview or else you should provide width for textview
<com.custom.views.CustomTextView
android:id="#+id/txt_send_chat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:maxWidth="250dp"
android:textColor="#color/color_chat_sender"
android:textSize="16sp"
app:font_name="#string/font_roboto_regular" />
You can use webview to achieve this behavior.
In webview you can use css to adjust text.
Take a look at this answer
Update
You can calculate width of string and add \n to string where is string needs to split
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
int height = bounds.height();
int width = bounds.width();
Results is in pixels, so just check width of your view or screen and split the string.
UPDAE2: Example Code
I just wrote an example with simple layout in activity onCreate you can implement it in adapter or whatever works for you.
TextView textView = (TextView) findViewById(R.id.txt); //textview with empty text
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
String text = "some long text here.....";// text data to work on
textPaint.getTextBounds(text, 0, text.length(), bounds);
int textWidth = bounds.width();// get text width in pixel
int marginPadding = 100;// we have some padding and margin from xml layouts
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int rootWidth = displayMetrics.widthPixels-marginPadding;// maximum width on screan
if (textWidth > rootWidth) { // check if need to split the string.
int lineMax = (text.length() * rootWidth) / textWidth; // maximum Characters for each line
String result = text.replaceAll("(.{" + String.valueOf(lineMax) + "})", "$1\n"); // regex to replace each group(lineMax) of Chars with group of char + new line
textView.setText(result);
} else
textView.setText(text);
UPDATE#3: Fixed code for Listview
onCreate
ArrayList<String> data = new ArrayList<>();
data.add("000");
data.add("aaaaaaaaaaa");
data.add("aaaaaaaaaaa bbbbbbbbbbbb");
data.add("aaaaaaaaaaa bbbbbbbbbbbb cccccccccccccccc");
data.add("aaaaaaaaaaa bbbbbbbbbbbb cccccccccccccccc ddddddddddddd");
data.add("aaaaaaaaaaa bbbbbbbbbbbb cccccccccccccccc ddddddddddddd eeeeeeeeeeeee");
data.add("aaaaaaaaaaa bbbbbbbbbbbb cccccccccccccccc ddddddddddddd eeeeeeeeeeeee ffffffffffffffffff");
data.add("aaaaaaaaaaa bbbbbbbbbbbb cccccccccccccccc ddddddddddddd eeeeeeeeeeeee ffffffffffffffffff gggggggggggggggg");
data.add("aaaaaaaaaaa bbbbbbbbbbbb cccccccccccccccc ddddddddddddd eeeeeeeeeeeee ffffffffffffffffff gggggggggggggggg hhhhhhhhhhhhhhhh");
ListView listView = (ListView) findViewById(R.id.listview);
MyAdapter adapter= new MyAdapter(data,this);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
MyAdapter.java
public class MyAdapter extends BaseAdapter {
private LayoutInflater inflater = null;
Context context;
ArrayList<String> data;
public MyAdapter(ArrayList<String> data, Context context) {
this.context = context;
this.data = data;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object getItem(int i) {
return data.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(final int i, View convertView, ViewGroup viewGroup) {
final View view = inflater.inflate(R.layout.item, null);
final TextView tv_text = (TextView) view.findViewById(R.id.tvValue);
if (data.get(i) != null) {
tv_text.post(new Runnable() {
#Override
public void run() {
//TextView is Ready to be used.
fixText(data.get(i),tv_text);
}
});
}
return view;
}
private void fixText(String text, TextView textView) {
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
int textWidth = bounds.width();// get text width in pixel
int marginPadding = 100;// we have some padding and margin from xml layouts
DisplayMetrics displayMetrics = new DisplayMetrics();
((MainActivity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int rootWidth = textView.getWidth();//displayMetrics.widthPixels - marginPadding;// maximum width on screan
if (textWidth > rootWidth) { // check if need to split the string.
//int lineMax = (text.length() * rootWidth) / textWidth; // maximum Characters for each line
//String result = text.replaceAll("(.{" + String.valueOf(lineMax-5) + "})", "$1\n"); // regex to replace each group(lineMax) of Chars with group of char + new line
String result = wrapText(rootWidth,text);
textView.setText(result);
} else
textView.setText(text);
}
private String wrapText(int textviewWidth,String mQuestion) {
String temp = "";
String sentence = "";
String[] array = mQuestion.split(" "); // split by space
for (String word : array) {
if ((temp.length() + word.length()) < textviewWidth) { // create a temp variable and check if length with new word exceeds textview width.
temp += " "+word;
} else {
sentence += temp+"\n"; // add new line character
temp = word;
}
}
return (sentence.replaceFirst(" ", "")+temp);
}
item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="1.0"
tools:background="#color/colorAccent">
<LinearLayout
android:id="#+id/flBubble"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:background="#color/colorPrimary"
android:layout_weight="0.9">
<ImageView
android:id="#+id/ivSay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="default_content_description"
android:padding="8dp"
android:src="#android:drawable/ic_media_play"
android:tint="#color/colorPrimaryDark" />
<TextView
android:id="#+id/tvValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"
android:textColor="#000000"
android:textSize="16sp"
tools:text="I would like to go to an Italian restaurant jkjk l;'"/>
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0.1"/>
</LinearLayout>
Try this
<TextView
android:id="#+id/tvValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp"
android:textColor="#color/black"
android:textSize="16sp"
tools:text="I would like to go to an Italian restaurant"/>
</LinearLayout>
You can try with
Autosizing TextViews
The Support Library 26.0 provides full support to the autosizing TextView feature on devices running Android versions prior to Android 8.0 (API level 26). The library provides support to Android 4.0 (API level 14) and higher. The android.support.v4.widget package contains the TextViewCompat class to access features in a backward-compatible fashion
For Example:
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:autoSizeTextType="uniform" />
For more details Guidelines go HERE
Their is Library too HERE
Try this
private String getWidthFitString(String input) {
Paint paint = text.getPaint();
// you can define max width by your self
int maxWidth = getContentMaxWidth();
float width = paint.measureText(input);
if (width > maxWidth) {
List<String> words = Arrays.asList(input.split("\\s"));
int breakLinePosition = 0;
String toBreakLineText;
List<String> toBreakLineWords = new ArrayList<>();
while (breakLinePosition < words.size()) {
toBreakLineWords.add(words.get(breakLinePosition));
toBreakLineText = TextUtils.join(" ", toBreakLineWords);
float currentWidth = paint.measureText(toBreakLineText);
if (currentWidth > maxWidth) {
break;
}
breakLinePosition ++;
}
if (breakLinePosition > 1) {
toBreakLineWords.remove(toBreakLineWords.size() - 1);
toBreakLineText = TextUtils.join(" ", toBreakLineWords);
List<String> fromBreakLineWords = new ArrayList<>();
for (int i = breakLinePosition; i < words.size(); i++) {
fromBreakLineWords.add(words.get(i));
}
return toBreakLineText + "\n" + getWidthFitString(TextUtils.join(" ", fromBreakLineWords));
} else {
return input;
}
}
return input;
}
For those whose string has non-breaking space character may try the following:
value.replace("\u00A0", " ")
Hope this might help
Change your TextView to EditText and put this 2 lines. it should help you
android:inputType="textMultiLine"
android:enabled="false"
This will place you text properly and later on you can give a edit feature in your application if you need.
My App has run out of memory due to a ListView with an ImageView and 2 TextView's inside. The images are big and I shrink them down to 56x56dp in single_row.xml for the ListView, but they are also used in larger sizes on other screens.
The MainActivity calls the single_row into the listview and inflates it. Then the single row is duplicated with individual content for each row. Each row has an image and 2 textviews. I know there's a way to use bitmap but I'm unsure how to implement it in the code.
Here is the main activity.java code
#Override public View getView(int i, View convertView, ViewGroup viewGroup) {
View row=convertView;
if(row==null){
LayoutInflater inflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row=inflater.inflate(R.layout.single_row, viewGroup,false);
}
TextView title = (TextView) row.findViewById(R.id.textView);
TextView description = (TextView)row.findViewById(R.id.textView2);
ImageView image = (ImageView)row.findViewById(R.id.imageView);
SingleRow temp=list.get(i);
title.setText(temp.title);
description.setText(temp.description);
image.setImageResource(temp.image);
return row;
}
here is the singlerow.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_margin="10dp"
android:src="#drawable/placeholder1" />
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="#+id/imageView"
android:layout_toRightOf="#+id/imageView"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/imageView"
android:layout_alignLeft="#+id/textView"
android:layout_alignParentRight="true"
android:text="Small Text"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>`
Any solution would be helpful. I'm unsure how to implement it in this specific structure.
As Karakuri said, restricting the ImageView size to 56dpx56dp doesn't mean that the entire bitmap is not loaded. First load the bitmap from the file (or resource), and shrink it. Use
//For resource
image.setImageBitmap(decodeSampledBitmapFromResource("android.resource://com.my.package/drawable/image_name"));
//For file
image.setImageBitmap(decodeSampledBitmapFromResource(filepath));
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 2;
if (height >= reqHeight || width >= reqWidth) {
inSampleSize *= 2;
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(String file,
int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(file, options);
}
In android, how can you create a scroll view that's got a max height, and wrap contents, basically it wraps the content vertically, but has a maximum height?
I tried
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="200dp"
android:layout_alignParentBottom="true" >
<LinearLayout
android:id="#+id/maincontainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
But this isn't working?
you can add this to any view (override onMeasure in a class inherited from a view)
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (maxHeight > 0){
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
switch (hMode){
case MeasureSpec.AT_MOST:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.AT_MOST);
break;
case MeasureSpec.UNSPECIFIED:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
break;
case MeasureSpec.EXACTLY:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.EXACTLY);
break;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
I've extended ScrollView and added code to implement this feature:
https://gist.github.com/JMPergar/439aaa3249fa184c7c0c
I hope that be useful.
You can do it programmatically.
private static class OnViewGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private final static int maxHeight = 130;
private View view;
public OnViewGlobalLayoutListener(View view) {
this.view = view;
}
#Override
public void onGlobalLayout() {
if (view.getHeight() > maxHeight)
view.getLayoutParams().height = maxHeight;
}
}
And add listener to the view:
view.getViewTreeObserver()
.addOnGlobalLayoutListener(new OnViewGlobalLayoutListener(view));
Listener will call method onGlobalLayout(), when view height will be changed.
It can be done by wrapping the view into ConstraintLayout and using layout_constraintHeight_max attribute.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHeight_max="wrap"
app:layout_constraintVertical_bias="0">
...
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
In the example above, the parent ConstraintLayout height is limited to 200dp, and the child ScrollView height wraps the content till it's less than 200dp. Note that app:layout_constraintVertical_bias="0" aligns the child ScrollView at the top of the parent, otherwise it will be centered.
for set scrollview height you must use 2 linerlayout inside together and then set scrool view as them child then set middle linerlayout layout:height for limit scrollview height.
1.) Create a class to handle setting maximum height to what is passed by the user:
public class OnViewGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private Context context;
private int maxHeight;
private View view;
public OnViewGlobalLayoutListener(View view, int maxHeight, Context context) {
this.context = context;
this.view = view;
this.maxHeight = dpToPx(maxHeight);
}
#Override
public void onGlobalLayout() {
if (view.getHeight() > maxHeight) {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = maxHeight;
view.setLayoutParams(params);
}
}
public int pxToDp(int px) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int dp = Math.round(px / (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
return dp;
}
public int dpToPx(int dp) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int px = Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
return px;
}
}
2.) Attach this to the view and pass the maximum height in DP:
messageBody.getViewTreeObserver()
.addOnGlobalLayoutListener(
new OnViewGlobalLayoutListener(messageBody, 256, context)
);
Thanks to #harmashalex for the inspiration. I made modifications to as setting the layout params didn't work by #harma's code. Also, dp-to-px conversion is necessary to offload wondering about it.
Here you can set height of your Scrollview like this -:
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentBottom="true" >
<LinearLayout
android:id="#+id/maincontainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
I have a TableLayout with 6 childs/entrys. These childs are a custom RelativeLayout. In each RelativeLayout is a big TextView in the middle and an ImageView and small TextView at the bottom.
The ImageView should be as tall as the TextView next to it. That's why I set the attribute ALIGN_TOP and ALIGN_BOTTOM to the TextView (you can see it in code below). This works very well and both - ImageView and TextView - have the same height now. But the problem is, that the left and right side of the ImageView don't "wrap content" anymore (as you can see on the screenshot).
Is there a way to fit the left and right side to the image and remove the "padding"?
Here is my code:
view_display_component.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:id="#+id/tvDisplayBig"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_weight="1"
android:gravity="center"
android:textColor="#color/white"
android:textSize="#dimen/font_size_extra_large" />
<ImageView
android:id="#+id/imageViewDisplayIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="#id/tvDisplayBig"
android:layout_gravity="bottom"
android:adjustViewBounds="true"
android:baselineAlignBottom="true"
android:scaleType="fitCenter"
android:src="#drawable/stopwatch_64"
android:visibility="visible" />
<TextView
android:id="#+id/tvDisplaySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="bottom"
android:includeFontPadding="false"
android:textColor="#color/white"
android:textSize="#dimen/font_size_small" />
</merge>
class DisplayComponent which extends RelativLayout
public DisplayComponent(Context context) {
super(context);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_display_component, this, true);
tvDisplay = (TextView) getChildAt(0);
icon = (ImageView) getChildAt(1);
tvName = (TextView) getChildAt(2);
setupAlign();
}
private void setupAlign() {
if(index % 2 == 0) { // LEFT SIDE
// same as "RIGHT SIDE"
} else { // RIGHT SIDE
RelativeLayout.LayoutParams paramsIcon = (RelativeLayout.LayoutParams) icon.getLayoutParams();
paramsIcon.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
paramsIcon.addRule(RelativeLayout.ALIGN_TOP, tvName.getId());
paramsIcon.addRule(RelativeLayout.ALIGN_BOTTOM, tvName.getId());
icon.setLayoutParams(paramsIcon);
RelativeLayout.LayoutParams paramsTvName = (RelativeLayout.LayoutParams) tvName.getLayoutParams();
paramsTvName.addRule(RelativeLayout.RIGHT_OF, icon.getId());
tvName.setLayoutParams(paramsTvName);
tvName.setBackgroundColor(Color.BLUE); // only for testing
icon.setBackgroundColor(Color.YELLOW);
}
I found an (ugly) solution. Because my icon is square, I created a custom ImageView and overrode the onSizeChanged() method like this:
public class IconImageView extends ImageView {
public IconImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(h != oldh && h > 0)
getLayoutParams().width = h; // same width as height
}
}
But this works only if the image is square. That's why I am still searching for a better solution. Maybe some layout solution with a better setting of alignment.
Best regards!