I read many articles about optimization and UI performance improvement of recyclerview. Their main point is make onBindViewHolder lighter.
List of issues I have :
My item layout has a frameLayout to show video with different width and height .I want to change the aspect ratio of frame layout according to video. My code inside onBindViewHolder is:
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) frameLayout.getLayoutParams();
if ((float) height / (float) width > (float) 1.25) {
params.dimensionRatio = "4:5";
} else {
params.dimensionRatio = width + ":" + height;
}
frameLayout.setLayoutParams(params);
This is the cause of lag. I can clearly observe. Tell me how instagram,Twitter show different ratio of video in their feed without lag
I have custom recylerView I'm creating a viewHolder instance inside it and performing some actions such as:
addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
#Override
public void onChildViewAttachedToWindow(#NonNull View view) {
VideoAdapter.ViewHolder holder = (VideoAdapter.ViewHolder) view.getTag();
holder.styledPlayerView.setPlayer(exoPlayer);
holder.framelayout.addView(holder.styledPlayerView);
}
I'm doing this inside the recyclerView class. I think it is a part of onBindViewHolder as the action is on current instance of viewHolder. here adding view to frame layout is expensive. How to handle this?
I'm using 4 shape drawable,4 vector drawable & 3 custom styles. I can clearly see that when I'm using drawable it is lagging but after removing drawable the lag is decreasing to 40-50%
Related
Into this adapter, I build dynamically the imageview size into the viewholder constructor:
ContentViewHolder(View view, Context context) {
super(view);
ButterKnife.bind(this, view);
// Set image width + height
mImageView.getLayoutParams().width = Math.round(UIUtils.getScreenWidth() / 4f);
mImageView.getLayoutParams().height = Math.round(
mImageView.getLayoutParams().width / Defines.FORM_LIST_FORMAT);
}
To show images, I use Glide (with crossfade effect; I tried without and the result is the same...).
And the result is not good: the first images have a bad image displaying...
Could you give some ways to fix it please?
Add android:scaleType="centerCrop" in your XML layout, or mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP) if you want to do it programmatically.
Problem description
LinearLayoutManager.scrollToPositionWithOffset(pos, 0) works great if the sum of RecyclerView's all children's height is big than screen height. But it does not work if the sum of RecyclerView's all children's height is small than screen height.
Problem description in detail
Let's say I have an Activity and a RecyclerView as it's root view. RecyclerView's width and height are both match_parent. This RecyclerView has 3 items and the sum of these 3 child view's height is small than screen height. I want to hide first item when Activity is onCreated. User will see second item at start. If user scroll down, he still can see first item. So I call LinearLayoutManager.scrollToPositionWithOffset(1, 0). But it won't work since the sum of RecyclerView's all children's height is small than screen height.
Question
How can I make RecyclerView scroll to specific position even though the sum of RecyclerView's all children's height is small than screen height.
Following is my code according to #Haran Sivaram's answer:
Item first = new Item();
Item second = new Item();
Item third = new Item();
List<Item> list = Arrays.asList(first, second, three);
adapter.add(list);
adapter.notifyDataSetChanged();
recyclerView.post(new Runnable() {
#Override
public void run() {
int sumHeight = 0;
for (int i = 0; i < recyclerView.getChildCount(); i++) {
View view = recyclerView.getChildAt(i);
sumHeight += view.getHeight();
}
if (sumHeight < recyclerView.getHeight()) {
adapter.addItem(new VerticalSpaceViewModel(recyclerView.getHeight() - sumHeight + recyclerView.getChildAt(0).getHeight()));
}
linearLayoutManager.scrollToPositionWithOffset(1, 0);
}
});
It worked. But has some small issues.
What you need to do is to increase the height of the recycler view to a minimum height which will allow scrolling and hide your first element (screen height + height of the first item). You can achieve this by adding a dummy element as the last element and setting it's height or you could also do this using padding/margins (Have not tried this).
This also needs to be done dynamically once the view is drawn (You can do it statically if you are aware of the sizes of each item beforehand - I will not recommend this method though).
Use an onGLobalLayoutListner to get a callback once the view is drawn, do your measurements here and update the height. Now the scroll with offset should work fine.
I have a dialog with a layout inside and a SurfaceTexture with a video stream. When I receive the width and height from the video, I resize my layout like this:
private void resizeView(final VideoFormatInfo info) {
final Size size = calculateSize(info.getWidth(), info.getHeight());
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
final ViewGroup.LayoutParams layoutParams = mInnerLayout.getLayoutParams();
layoutParams.width = size.x;
layoutParams.height = size.y;
Log.i(TAG, String.format("run: setting innerlayout bounds to %d,%d", size.x, size.y));
mInnerLayout.setLayoutParams(layoutParams);
}
});
}
Now I have a fullscreen button that is supposed to resize the layout to the whole screen. But when I press it, the layout remains in a small area of the screen.
When I check the log the proper value on size.x and size.y is there (the bounds of the screen), yet the layout is not properly resized.
The innerlayout is added to a customView named "VideoPlayer". I set the color of the videoplayer background to red so when I switch to fullscreen the whole screen turns red, except for the video stream in the middle. This means that the underlying view is being properly resized but the innerLayout is not for some reason.
Funny thing is, I have another layout over the video render that creates a "flash effect" to simulate a camera flash when taking a snapshot. When that flash effect is triggered, then the video is resized to the whole screen.
So this is my layout tree:
VideoPlayerView (CustomView, not VideoView)
innerLayout (RelativeLayout)
videoSurfaceTexture (SurfaceTextureView)
flashLayout (RelativeLayout)
I also set this for debugging:
#Override
public void onSurfaceTextureSizeChanged(final SurfaceTexture surfaceTexture, final int width, final int height) {
Log.d(TAG, "onSurfaceTextureSizeChanged size=" + width + "x" + height + ", st=" + surfaceTexture);
Log.i(TAG, String.format("innerlayout bounds are %d,%d", mInnerLayout.getLayoutParams().width, mInnerLayout.getLayoutParams().height));
}
And the values on the inner layout are the proper values (those of the whole screen) when I press fullscreen, but the layout is not resized. I can tell it's the layout not being resized because I changed its background color to green and added some padding and I can see it in the center of screen taking a small space.
It looks as though somehow the view is not being updated with the layout changes.
I am running out of ideas here. I tried invalidate(), postInvalidate() and forceLayout() but those dont work.
You missed one important part of forceLayout():
This method does not call requestLayout() or forceLayout() on the parent.
So make the parent do a layout as well:
mInnerLayout.setLayoutParams(layoutParams);
mInnerLayout.forceLayout();
mInnerLayout.getParent().requestLayout();
final ViewGroup.LayoutParams layoutParams = mInnerLayout.getLayoutParams();
layoutParams.width = size.x;
layoutParams.height = size.y;
Log.i(TAG, String.format("run: setting innerlayout bounds to %d,%d", size.x, size.y));
ViewGroup parent = ((ViewGroup)mInnerLayout.getParent());
parent.removeView(mInnerLayout);
mInnerLayout.setLayoutParams(layoutParams);
parent.addView(mInnerLayout);//you might need to get the index so you slot it in there.
This will do. -(all thoughts)
EDIT
i didnt want to add explanation because it was all thoughts and i needed verifying if it will work
But the explanation for my code is LayoutParams are what the Parent uses to layout its children hence it is useful only in the laying out pulse or time.
Changing the layoutParams object makes the View object dirty, other factors need to be met before a dirty View is layed out, so that is why the values change but the View is not changed.
you could have also just called View.invalidate() and View.requestLayout() on that particular View or Parent and it will also solve your problem, calling View.invalidate() alone will not do instantly for you. eg
layoutParams.width = size.x;
layoutParams.height = size.y;
Log.i(TAG, String.format("run: setting innerlayout bounds to %d,%d", size.x, size.y));
//re-setting the layout params is also not neccessary
mInnerLayout.invalidate();
mInnerLayout.requestLayout();
The reason the first approach solves your problem is because the View is remove and added which calls for a Laying out to be processed
:) also you should have just accepted it and let the bounty period elapsed :)
use Inflator like
View view = inflater.inflate( R.layout.item /* resource id */,
MyView.this /* parent */,
false /*attachToRoot*/);
for more check Layout params of loaded view are ignored
I have a problem with my Recycler view and StaggeredGrid which cut the width by 2.
I load images into items with Picasso and when I load image first time, they are disposed strangely in the recycler view.
After reloading, everything seems good.
I think problem come from image loading : the StaggeredGrid doesn't know the image height the first time, but know after reloading because of cache.
How can i solve this problem ?
I think you have answered your own question. You need to load the images/determine their dimensions before adding the data to the recycler.
Solved by changing gap strategy to :
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
manager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
recyclerView.setLayoutManager(manager);
Change the position of items automatically
This happens because the holder dose not recognize the width and height of the Image view when you scroll up and down. It clears the upper view when you scroll down and vice versa.
Use like this :
#Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
MyViewHolder vh = (MyViewHolder) viewHolder;
ImageModel item = imageModels.get(position);
RelativeLayout.LayoutParams rlp = (RelativeLayout.LayoutParams)vh.imageView.getLayoutParams();
float ratio = item.getHeight() / item.getWidth();
rlp.height = (int) (rlp.width * ratio);
vh.imageView.setLayoutParams(rlp);
vh.positionTextView.setText("pos: " + position);
vh.imageView.setRatio(item.getRatio());
Picasso.with(mContext).load(item.getUrl()).placeholder(PlaceHolderDrawableHelper.getBackgroundDrawable(position)).into(vh.imageView);
}
For clear see this link: Picasso/Glide-RecyclerView-StaggeredGridLayoutManager
I have a very simple RelativeLayout subclass that adds an image view with a text view on top of it. I have a method, show(), which creates and adds the child views and sets the initial text.
At the point I call show() for the first time, the view does not know how big it is, so I can't set the textSize nor the padding for the textView.
I have a solution that mostly works, where I call setTextSize() and setPadding() for the textView within the overridden method, onSizeChanged(). The text does not show the first time it is displayed. However, it shows every time after that, perfectly sized and placed.
Here is the code for onSizeChanged():
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.e(TAG, "onSizeChanged() called");
if (_childTextView != null) {
float textSize = h / 2.0f;
int topPadding = (int)(h / 3.0f);
Log.e(TAG, "setting textSize = " + textSize);
Log.e(TAG, "topPadding = " + topPadding);
_childTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
_childTextView.setPadding(0, topPadding, 0, 0);
}
super.onSizeChanged(w, h, oldw, oldh);
Log.e(TAG, "end onSizeChanged()");
}
The code for show() is as follows:
public void show(int val) {
_val = val;
Log.e(TAG, "in show(), val = " + val);
// create and add background image if not already there
if (_backgroundImageView == null) {
_backgroundImageView = new ImageView(_context);
_backgroundImageView.setImageResource(R.drawable.background);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
params.addRule(CENTER_IN_PARENT);
addView(_backgroundImageView, params);
}
// create and add text view if not already there
if (_childTextView == null) {
_childTextView = new TextView(_context);
_childTextView.setTextColor(Color.BLACK);
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(CENTER_HORIZONTAL);
addView(_childTextView, params);
}
Log.e(TAG, "setting text to: " + _val);
// update value and make visible
_childTextView.setText(String.valueOf(_val));
setVisibility(VISIBLE);
Log.e(TAG, "end show()");
}
The background image displays correctly every time. The textView only displays correctly the second time show() is called and afterwards. Logging in onSizeChanged() shows that the calculated numbers are correct the first time. As expected, onSizeChanged() only gets called the first time, a bit after we return from show(). Subsequent calls to show() just set the value and visibility, and the text is displayed correctly.
My question is: is there a better way to do this? Or a better callback method to override?
Trying to set these values in show() doesn't work because the main view doesn't yet know its own size (at least the first time). I have tried putting invalidate() at the end of onSizeChanged(). I have also tried putting the call to setText() there.
I need to be able to do this based on size, because this class is reused in different contexts where the image needs to be smaller or larger.
Thank you for any insight you can give. I'd really like to keep this simple if possible.
Edit: What I am trying to do is size some text to be about 1/2 the size of the child image (which is the same as the parent size), and to have top padding set to about 1/3 of the image size. This would be easy if I just wanted it to be one size. However, I want it to be size-adjustable based on the needs of the display.
Imagine a postage stamp, where you want to place the value somewhere precisely in the image. So far so good. But what if this postage stamp needs to be displayed at different sizes on the same phone? You'd want both the placement offset (the padding) and the text size to adjust accordingly. If I hardcode this into the xml, then the text size and placement will not be adjusted when I size the layout. The text will be too big on the small version, and will be placed too far from the top of the image.
i have no idea why you override onSizeChanged(), normaly android handles all this nicely if you use it the way it is intendet.
can you pls explain what you want to achive - maybe with example picture?
however i wondered that you don't override onMesure() when you override the rest and if a delayed call to show() helps it also might be because of onMesure is called in between.
edit:
in android you should never want to know a real size of some views. nearly every device has other sizes and there is portrait/landscape mode too. if you start coding vs real sizes you can give up at the start. instead you should use something more relative like dp and sp than you should never again worry about text sizes and similar.
you may also want and you should use LayoutInflater and xml files as much as possible in your application. in a Activity you can call setContentView(). in other cases there might be methods to overload like onCreateView. and if you have nothing else you can do it like this:
LayoutInflater inflater = LayoutInflater.from(contextEgActivity);
View rootView = inflater.inflate(R.layout.highscore_daily, parentCanBeNull);
edit II:
so this is what you want - right? (on the ImageView and the TextView it would be even better to use wrap_content for height and width)
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:id="#+id/imageView"
android:layout_gravity="center"
android:background="#ff0000" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:text="New Text"
android:id="#+id/textView"
android:layout_gravity="center"
android:background="#00ff00"
android:textSize="20sp" />
</FrameLayout>
if you only have ~3 different sizes i would write 3 different xml files to match what you want. otherwise i think this code will fit your needs.
//http://stackoverflow.com/questions/4605527/converting-pixels-to-dp
public static float convertDpToPixel(float dp, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
float px = dp * (metrics.densityDpi / 160f);
return px;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//loads the xml above
ImageView v = (ImageView) findViewById(R.id.imageView);
int dp = 200;
int px = (int) convertDpToPixel(dp, this);
v.setMaxHeight(px);//no need for it
v.setMinimumHeight(px);//should do more or less the same as next line
v.setLayoutParams(new LinearLayout.LayoutParams(px, px));//is like android:layout_width="200dp" android:layout_height="200dp"
v.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, px));//is like android:layout_width="wrap_content" android:layout_height="200dp"
//basically you can do the same with the TextView + the Text styling
TextView tv = (TextView) findViewById(R.id.textView);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 50);
tv.setPadding(30,30,30,30);//don't forget, this is also px so you may need dp to px conversion
}
this is the normal way, nice clean and easy. if you why ever still want to react on size changes of your parent you can try this but i don't suggest it. btw changing view stuff should only be executed from ui/main thread so if the method gets called from a other thread thry a Handler like new Handler(getMainLooper)