Overview:
I have a ListView of different types of rows including images and text of varying heights. At runtime, I am trying to dynamically set the heights of the layout containing the image based on the width of the row. So the row's width is set to match_parent (because we want it to fill the width of the device), but the height must be calculated at runtime to get a specific aspect ratio for the frame.
Here's the code snippet from the Adapter that I'm using:
#Override
public View getView(View convertView) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(
R.layout.chat_row_image, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ViewTreeObserver observer = holder.videoFrame.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
frameWidth = holder.container.getWidth();
int width = frameWidth;
int height = (int) (frameWidth / Utilities.MEDIA_CELL_ASPECT_RATIO);
Log.d(TAG, "Calculated : " + width + "," + height);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, height);
holder.messageImage.setLayoutParams(params);
holder.videoFrame.getViewTreeObserver().removeGlobalOnLayoutListener(
this);
Log.d(TAG, "Imageview : " + holder.messageImage.getWidth() + "," + holder.messageImage.getHeight());
}
});
return convertView;
}
The problem: this code works well but during scrolling through the ListView, there are UI glitches that are readily noticeable. The main UI bug is that the rows have a pre-defined height from the XML file and are instantly transitioning to the new height we've calculated once the image finishes loading. This causes the entirety of contents below that row to suddenly shift as soon as the row appears on the screen. Also, we're scrolling from the bottom to the top (this is a chat thread).
Things I've tried:
We tried scrolling through the entire ListView via "setSelection" method (from ListView) to "preload" the contents before the user sees them. That didn't work and it doesn't seem like the images are loaded during the brief time we're scrolling past them.
We've tried setting a static XML file width and height, and that fixes the issue, but doesn't account for different screen sizes.
What I need help with: We want to keep the scrolling smooth even while resizing the layout dynamically. The final resort would be to create different XML files for each screen size category and hard code the height, but that would not keep our frame's aspect ratio as we desire.
Please let me know if there are any clarifications you would want. Thanks in advance!
The answer was to set the dimensions of the frame in the constructor of the Adapter instead of the getView() method. Based on Nannuo Lei's suggestion, we are not using the ViewTreeObserver.OnGlobalLayoutListener(), instead we are calculating the dimensions of the frame based on the display's width and padding/margin of other layout elements.
public ImageChatRow(Context context) {
this.mContext = context;
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
displayWidth = size.x - 40; //padding is 40 in our case
displayHeight = (int) (displayWidth / Utilities.MEDIA_CELL_ASPECT_RATIO);
}
Then in the getView() method, we just set the dimensions of the frame directly after inflating the view.
public View getView(View convertView) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(
R.layout.chat_row, null);
holder = new ViewHolder(convertView);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(displayWidth, displayHeight);
layoutParams.gravity = Gravity.CENTER;
holder.frame.setLayoutParams(layoutParams);
convertView.setTag(holder);
} else
holder = (ViewHolder) convertView.getTag();
This solves the problems of the glitches in loading rows and dynamically setting their heights. We haven't observed any jumpiness in the ScrollView since implementing this code!
You can add a LinearLayout outside in chat_row_image.xml, like this
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/layout_ll
android:layout_width="wrap_content"
android:layout_height="wrap_content">
[...]
</LinearLayout>
</RelativeLayout>
In Adapter.java
#Override
public View getView(View convertView) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(
R.layout.chat_row_image, null);
holder = new ViewHolder(convertView);
LinearLayout ll = (LinearLayout) view.findViewById(R.id.layout_ll);
LayoutParams linearParams = (LayoutParams) ll.getLayoutParams();
linearParams.width = width;
linearParams.height = height;
ll.setLayoutParams(linearParams);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
return convertView;
}
Related
How can i have 2 by 2 scrollable Grid? Which control should i use?
I tried Grid-View and as Grid-View can not have fixed rows so should i use Table-Layout or Grid-Layout ? or use linear-Layout in Grid-View ?
I want image to be displayed without being scaled down or scaled up so i am using AUTO_FIT and i get three rows with the below code but i want only two rows and two columns
#Override public View getView(int position, View convertView, ViewGroup parent)
{
ImageView view;
if (convertView == null)
{
view = (ImageView) convertView;
view = new ImageView(context);
view.setAdjustViewBounds(true);
view.setLayoutParams(new GridView.LayoutParams(GridView.AUTO_FIT, GridView. AUTO_FIT));
}
else
view = (ImageView) convertView;
String url = getItem(position);
Picasso.with(context).load(url).error(R.mipmap.no_photo).fit().into(view);
return view;
}
Below is the sample attached image
I would recommend you use a RecyclerView with a GridLayoutManager. This will allow you to specify the number of columns:
recyclerView.setLayoutManager(new GridLayoutManager(context, 2));
If you would like to set the row height so that two rows always match the height of your screen, you could set it inside onBindView() of your adapter. You can ascertain the relative row height by subtracting the screen height from the height of your Toolbar:
//Could calculate this in your `Activity` class and pass the value to your Adapter
public int getProprtionalRowHeight() {
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int height = displaymetrics.heightPixels;
return ((height - toolbar.getHeight()) / 2);
}
In your adapter:
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
//...
ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
params.height = valueReturnedFromGetProprtionalRowHeight;
holder.itemView.setLayoutParams(params);
}
you can use StaggeredGrid for this purpose its stable and easy to use.
you can also use StaggeredGridLayoutManager with recycler view.
I have a ListView that every row shows different percentage.
and I wanna change one of inner layouts width (inside inflated counterView) for this purpose .
for example:
if first value item in listview is 10% then width set to 10px too
if second value item is 50% then width change to 50px too
something like this:
How can I do that ?
I tried these codes but it doesn't work and the width doesn't change:
(I defined match_parent width for layout inside XML and I wanna change it programmatically)
public class MyAdapterScores extends BaseAdapter {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.scores_layout,null,true);
//I wanna change 'relativeLayoutRightSqure' width inside 'scores_layout'
View layout = convertView.findViewById(R.id.relativeLayoutRightSqure);
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) layout.getLayoutParams();
//variable precentage has value
p.width=precentage ;
layout.requestLayout();
You can't just set the width to a percentage I think. I haven't tested this solution, but try it out! Might work!
One solution could be to set the width to a percentage of the total width of the display maybe:
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int layoutWidth = size.x * (percentage / 100)
Then you can use that variable "layoutWidth" to set the width on the layout you want.
please use the following code
RelativeLayout layout = convertView.findViewById(R.id.relativeLayoutRightSqure);
RelativeLayout.LayoutParams param=layout.getLayoutParams();
param.width=precentage ;
layout.setLayoutParams(param);
I found the problem !
the problem was defining android:layout_toRightOf and android:layout_toLeftOf for my inner counterView layout view simultaneously that causes setting width doesn't work !!
(
Referred to above code (relativeLayoutRightSqure):
View layout = convertView.findViewById(R.id.relativeLayoutRightSqure);
)
this is the code in my custom adapter (THE CODE IN BROWN COLOR) when initially list is build proper margin is applied to valid items when i scroll down and again scroll up all the rows in list shifts the margin left by 20 what i'm doing wrong please reply soon
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
// getting data
final ViewMovieDetailsModel_ViewComments movies = getItem(position);
if (convertView == null)
{
convertView = View.inflate(context, R.layout.comment_row, null);
holder = new ViewHolder();
//getting handles
holder.comments_linearLayout = (LinearLayout) convertView.findViewById(R.id.comments_linearLayout);
holder.commenter_textView = (TextView) convertView.findViewById(R.id.comment_row_commenter);
holder.commented_on_textView = (TextView) convertView.findViewById(R.id.comment_row_comment_time);
holder.comment_text_textView = (TextView) convertView.findViewById(R.id.comment_row_comment_text);
holder.reply_button = (Button) convertView.findViewById(R.id.comment_row_reply_button);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder)convertView.getTag();
}
if (movies != null)
{
if (((movies.getParent_comment_id()).toString().equals("null")) && session.isLoggedIn()==true) {
holder.reply_button.setVisibility(View.VISIBLE);
}else{
holder.reply_button.setVisibility(View.INVISIBLE);
}
`if (!((movies.getParent_comment_id()).toString().equals("null")))
{
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT);
params.setMargins(20, 0, 0, 0);
holder.comments_linearLayout.setLayoutParams(params);
}`
holder.commenter_textView.setText(movies.getUsername_commentor());
holder.commenter_textView.setTag(movies.getUser_id_commentor());
return convertView;
}
because you are setting margins (the brown font) in 'if' statement:
if (movies != null)
just take it out of this if block (for example put it just before the return point)
Right now this code is probably not executed at the first view load, since the movie is null. When the getView is called second time, the movie is not null, and the marigin is set according to your 'brown' code.
if this is not the solution - maybe the inside if statement condition is not true (the one that is in first 'brown' row). so.. your own logic prevents the marigins to be set as you want :)
Please let me know if it helps.
One way you could go about solving this problem is instead of using LayoutParams.setMargins(20, 0, 0, 0), you could create an empty TextView whose width is 20 dp by default and whose position will be to the left of your rows contents. It will be View.GONE by default, but when if (!((movies.getParent_comment_id()).toString().equals("null"))) happens, you can set that to View.VISIBLE
I am trying to change gridView column number. I call setNumColumn() and invalidateViews() to update the view.
However, the grid's cell width will not change dynamically. I set the stretchMode="columnWidth", but it didn't work.
Problem Solved:
The child view in the grid view do not re-calculate its layout when the grid view changes the column on runtime.
In your BaseAdapter, you should call forceLayout() to calculate its layout.
#Override
public View getView(final int position, View convertView,
ViewGroup parent) {
if (null == convertView) {
convertView = mInflater.inflate(
R.layout.mnm_customer_user_thumbnail, null);
control.txtUserName = (TextView) convertView
.findViewById(R.id.mnmTxtStudentName);
control.image = (ImageView) convertView
.findViewById(R.id.mnmImageStudent);
control.imageCheckBox = (ImageView) convertView
.findViewById(R.id.mnmImageCheckBox);
control.mnmOuterBounder = (RelativeLayout) convertView
.findViewById(R.id.mnmOuterBound);
convertView.setTag(control);
} else {
convertView.forceLayout();
}
...
...
...
...
Thanks
May be you could get help through this nice sample offered by Google
http://developer.android.com/shareables/training/BitmapFun.zip
The following code which locate in the ImageGridFragment.java is used to handle column number change dynamically during runtime such as screen orientation change when rotate the device:
Here is the comments: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 nic square thumbnails.
mGridView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (mAdapter.getNumColumns() == 0) {
final int numColumns = (int) Math.floor(
mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing));
if (numColumns > 0) {
final int columnWidth =
(mGridView.getWidth() / numColumns) - mImageThumbSpacing;
mAdapter.setNumColumns(numColumns);
mAdapter.setItemHeight(columnWidth);
if (BuildConfig.DEBUG) {
Log.d(TAG, "onCreateView - numColumns set to " + numColumns);
}
}
}
}
});
return v;
in xml u have to provide this attribute..
android:stretchMode="columnWidth" . In your adapter depending on the size(by `getCount()`) of the list colomn will create dynamically..
While scrolling the gallery last item is scrolled and stops at left margin. I want to stop the last item at right margin of the screen. i am showing 3 items at a time on screen thanks for any suggestions.
public View getView(int position, View convertView, ViewGroup parent) {
Gallery newArticleRow;
LinearLayout layout = (LinearLayout) convertView;
if (layout == null) {
Log.d("in home", "HomeActivityListViewAdapter--- new getView:" + position);
// Inflate the news article row
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.news_articles_row, null);
newArticleRow = (Gallery) convertView
.findViewById(R.id.news_articles_gallery);
DisplayMetrics metrics = new DisplayMetrics();
FishwrapHomeActivity.this.getWindowManager().getDefaultDisplay()
.getMetrics(metrics);
MarginLayoutParams mlp = (MarginLayoutParams) newArticleRow
.getLayoutParams();
mlp.setMargins(-(metrics.widthPixels - (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 90, FishwrapHomeActivity.this
.getResources().getDisplayMetrics())), mlp.topMargin,
mlp.rightMargin, mlp.bottomMargin);
newArticleRow.setId(position);
newArticleRow.setLayoutParams(mlp);
convertView.setTag(newArticleRow);
convertView.setBackgroundColor(Color.WHITE);
convertView.setFocusableInTouchMode(false);
} else {
Log.d("in home", "HomeActivityListViewAdapter--- old getView:" + position);
// Get the newArticleRow HorizontialListView
newArticleRow = (Gallery) convertView.getTag();
}
// Set the adapter only if the ArticleRow
if (newArticleRow.getAdapter() == null || newArticleRow.getAdapter() != rowAdapters.get(position)) {
newArticleRow.setAdapter(rowAdapters.get(position));
newArticleRow.setOnItemClickListener(itemListener);
}
// Debug.stopMethodTracing();
return convertView;
}
#Rekha i think you should stop further scrolling functionality in that direction when position = lastitem - 1 element. You might want to do that through your Gallery instance ( gallery.getSelectedItemPosition() ) and the onscrollistener.
by the way, does this solve your previous "position restarts gallery in listview" issue? i have a similar issue that repeats my gallerys within my list when using a gallery holder.