Scroll effect for RecyclerView - android

I am using horizontal RecyclerView in Android app.
Showing two items (ImageViews) on the screen at a time.
To do this I am setting the width of each ImageView to half of the screen in ViewHolder class of the adapter:
public static class ViewHolder extends RecyclerView.ViewHolder {
private LinearLayout ll_Img;
private ImageView iv_ad;
private ViewHolder(View v) {
super(v);
ll_Img = (LinearLayout) itemView.findViewById(R.id.ll_Img);
ll_Img.getLayoutParams().width = (Utils.getScreenWidth(itemView.getContext()) / 2);
iv_ad = (ImageView) v.findViewById(R.id.iv_main_ad);
}
}
And getting screen's width:
public static int getScreenWidth(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int screenWidth = size.x;
return screenWidth;
}
It works fine but I want to implement scroll effect like on iOS,
to scroll one item per swipe,
so after each swipe 2 items must fit the screen width.
By the way, two videos worth 1000 words
so here what I am having now:
https://drive.google.com/file/d/0B7j1Rf_oUEbLOWk1OUtpWXFpcEE/view?usp=sharing
And what I want to achieve (as my colleague implemented on iOS):
https://drive.google.com/file/d/0B6B-4-ITg1EQTElNTWsxMWg4aWs/view?usp=sharing
Each piece of advice is appreciated.

This is a viewpager, not a recyclerview. This link might help https://commonsware.com/blog/2012/08/20/multiple-view-viewpager-options.html
Just for anyone else's benefit:
getPageWidth() returns a floating-point number, between 0 and 1,
representing the portion of the width of the ViewPager that a given
page should take up. By default, the page width is 1, but by
overriding this, you can have multiple pages on the screen
simultaneously.

Not entirely sure if this would give the desired effect, but if you are using a linearlayoutmanager, you could add a scrolllistener to your recyclerview, and whenever it gets called, use the linearlayout's method scrollToPosition to force it to scroll all the way to the next item.

Related

How to set recycler height to highest item in recyclerView?

I need to make sure that horizontal recyclerView height is the same as the height of the biggest item.
Items can have a different height (Item = always the same image + title + subtitle, title and subtitle could have infinite length).
When I set wrap_content for my recyclerView it would resize, basing on the height of visible items which makes content below recyclerView jump, and that's something I want to avoid.
What I want to achieve:
The gray area is visible viewport.
So basically I would like to get somehow hight of the biggest item, then put recyclerView height to that number.
What I already tried is approximation high of items based on length of title + subtitle but it's very inaccurate because for example even if two titles have the same text length they could have different width because of font that I use which is not a monospace font.
I just had this issue as well. My solution is:
Wrap the RecyclerView inside a ConstraintLayout.
Set the ConstraintLayout's layout_height to wrap_content.
Add an item view to the ConstraintLayout and populate it with the data of the item you expect to be the highest based on the length of its title for example.
Set the item view's visibility to invisible.
Set the RecyclerView's layout_height to zero, and make its top and bottom constraints match that of the item view.
Too late for an answer, but maybe this will help someone.
I struggled with the same issue and couldn't find an acceptable solution.
Solved by following:
First, you need to override onMeasure from the RecyclerView to save the largest element height:
class CustomRecycleView(ctx: Context, attrs: AttributeSet) : RecyclerView(ctx, attrs) {
private var biggestHeight: Int = 0
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
val h = child.measuredHeight
if (h > biggestHeight) biggestHeight = h
}
super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(biggestHeight, MeasureSpec.EXACTLY))
}
}
In you layout replace RecycleView with this CustomRecycleView:
onMeasure is called when a new element in the list is visible, and if the element is the highest, then we save this value. For example: if the first element has lowest height but lates has highest then at start RecycleView will be have height match to first element but after scrolling it will stay match to highest.
If you don't need to make RecycleView height match to highest item at start then you can stop here.
To do this at the beginning, you must make a hack (based on #MidasLefko suggestion):
To find out initially what the height of the highest element will be, you need to add a scroll mechanism to the end and the beginning. I did it as follows:
private fun initRecycleView(items: ArrayList<Object>) {
val adapter = Adapter()
rv.visibility = View.INVISIBLE
rv.vadapter = adapter
rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
rv.setHasFixedSize(true)
rv.smoothScrollToPosition(pinnedPosts.size)
Handler().postDelayed({
rv.smoothScrollToPosition(0)
}, 300)
Handler().postDelayed({
rv.visibility = View.VISIBLE
}, 700)
}
Set the visibility of Recycle view to INVISIBLE and after 700 milliseconds to VISIBLE to make this process invisible for user. Also, scrolling to start is performed with a delay of 300 milliseconds, because without some delay it can work incorrectly. In my case, this is needed for a list of 3 elements, and these delays is optimal for me.
Also remember to remove all Handler callbacks in onStop ()
I don't think that this is possible out of the box.
Let's think for a minute about how a RecyclerView works. In order to save memory it reuses the same View objects and just binds them to new data from the list as the user scrolls. So, for example, if the user sees item's 0 and 1 then the system has only measured and laid out 2 items (and perhaps one or two more to help scroll performance).
But let's say that your tall item is number 50 in the list, when the RecyclerView binds the first few items it has no idea at all that item 50 even exists, let alone how tall it will be.
However, you can do something a bit hacky. For example, you can measure each items height after it is bound, keep track of the tallest, and then manually set the RecyclerView height to that size. With that mechanism in place you can make the RecyclerView be hidden, then manually scroll to the end of the list, scroll back to the beginning of the list, then show the RecyclerView.
Not the most elegant solution, but it should work.
Created a method to calculate the projected height of textView by trying all the description in the list to get the highest height.
public static int getHeightOfLargestDescription(final Context context, final CharSequence text, TextView textView) {
final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Point displaySize = new Point();
wm.getDefaultDisplay().getSize(displaySize);
final int deviceWidth = displaySize.x;
textView.setTypeface(Typeface.DEFAULT);
textView.setText(text, TextView.BufferType.SPANNABLE);
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
textView.measure(widthMeasureSpec, heightMeasureSpec);
return textView.getMeasuredHeight();
}
then used this method to in onCreateViewHolder to get ready with the highest height to be used while binding the view.
MyViewHolder myViewHolder = new MyViewHolder(itemView);
for (Model m : modelList) {
currentItemHeight = getHeightOfLargestDescription(context, m.description, myViewHolder.description);
if (currentItemHeight > highestHeight) {
highestHeight = currentItemHeight;
}
}
Then used this highestHeight in onBindViewHolder` to set the height of the description TexView, so that all the views always have the same height that is equal to the highest height.
viewHolder.description.setHeight(highestHeight);
Code is committed in the
https://github.com/dk19121991/HorizontalRecyclerWithDynamicHeight
Let me know if this solves your problem, if you have some more question feel free to ask.
Thanks
To view a full discussion on this solution please see below
https://stackoverflow.com/a/67403898/4828650
You may try this:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
final int newHeight = recyclerView.getMeasuredHeight();
if (0 != newHeight && minHeight < newHeight) {
// keep track the height and prevent recycler view optimizing by resizing
minHeight = newHeight;
recyclerView.setMinimumHeight(minHeight);
}
}
});
you should try with different item_view type
Try this
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = mLayoutInflater.inflate(R.layout.view_item, parent, false);
// work here if you need to control height of your items
// keep in mind that parent is RecyclerView in this case
int height = parent.getMeasuredHeight() / 4;
itemView.setMinimumHeight(height);
return new ItemViewHolder(itemView);
}
Or you can try this also
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.itemview, parent, false);
ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
layoutParams.height = (int) (parent.getHeight() * 0.3);
itemView.setLayoutParams(layoutParams);
return new MyViewHolder(itemView);
}
You can also set your itemView with fixed height.
I disabled the recycling in recycler view and it solved the issue.
recyclerView.getRecycledViewPool().setMaxRecycledViews(TYPE_CAROUSEL, 0);
this solution may have a performance issue if there are a lot of items but will work fine for a few items lets say 5 to 20 which was case for me.
recyclerViewHorizontal.setMinimumHeight(maxItemHeight) has worked well for me.

Android: How to add and stack multiple textviews dynamically on the screen

In my app, user can add a tag in EditText. After hitting "Enter", the tag will be added to the screen. Each tag can have different lengths. User can add as many as he wants. What I want is: one TextView is added after another. When they fill one line, it will start a new row, then another row. Like below (two rows of TextViews):
"text 1","text 2","text 3","Another text"
"one","Monday"
Do I have to get the widths of the screen and every TextView and then do some calculations? Or is there a graceful way to do this?
You can use a RelativeLayout as a container and you should add TextViews as programmatically. While adding TextViews, you need to calculate screen width and each TextViews width. If last added TextView width exceeds the limit, you need to move that to next row.
You can get Screen Width like below:
public static int getScreenWidth(Context ctx) {
int width = 0;
try {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) ctx
.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
width = metrics.widthPixels;
} catch (Exception e) {
e.printStackTrace();
}
return width;
}
But, there are many libraries to achieve that. You do not have to make this from scratch.
https://github.com/Cutta/TagView
https://github.com/kaedea/android-tagview
https://github.com/whilu/AndroidTagView
https://github.com/mcharmas/android-tagview

Layout width and height being ignored

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

Programmatically add items to scrollview top, but should not scroll to top

I have scrollview with child LinearLayout . I am adding data programmaticaly to it. When i add some data to top of linearlayout it automatically scrolls to top element. But i want something like , user reaches top -> scrolls upside to load previous data ->add data to linearlayout top but should not get focus, after addition complete , if user scrolls then and then only it should display .
How to achieve this?
Well I thought of a way and it works almost perfectly.
I have a LinearLayout (llCommunicationsLayout) inside a ScrollView (svCommunications) .
I inflate a new LinearLayout, I'm going to add views to the top of this new LinearLayout and then add this new layout to the LinearLayout inside the ScrollView.
This is the new layout:
final LinearLayout wrapperLayout = (LinearLayout) mInflater.inflate(R.layout.empty_layout, null);
I add my views to the 0'th position of this layout.
wrapperLayout.addView(view, 0);
After all the views are added into the wrapperLayout, I add the wrapperLayout into the llCommunicationsLayout (the one inside my ScrollView)
llCommunicationsLayout.addView(wrapperLayout, 0);
After this, I calculate their heights after the wrapperLayout is on screen (has a measurable height)
wrapperLayout.post(new Runnable() {
#Override
public void run() {
int wrapperHeight = wrapperLayout.getHeight();
int svHeight = svCommunications.getHeight();
int scrollHeight = Math.abs(svHeight - wrapperHeight);
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int displayHeight = displaymetrics.heightPixels / 4;
svCommunications.scrollTo(0, (scrollHeight + displayHeight));
}
});
Here, I get both the newly added layout's and the ScrollView's heights.
I calculate their difference, add the 1/4 of the height of the screen of my device and scroll it, voila!
It's not perfect, but after the layouts are added, it no longer scrolls to the top of the screen. Experiment with the displayHeight for different results.
Hope this helps someone out.
You can grab the current view which is on top of your LinearLayout then add new content to your LinearLayout and then scroll back to view which was previously on top. The code would be something like:
public void addViewsOnTop(List<View> views) {
final View currentViewOnTop = (linearLayout.getChildCount() > 0) ? linearLayout.getChildAt(0) : null;
// Add Views. Note that views will appear in reverse order
for(View view : views) {
linearLayout.addView(view, 0);
}
// Scroll back to view which was on top
if(currentViewOnTop != null) {
new Handler().post(new Runnable() {
#Override
public void run() {
scrollView.scrollTo(0, currentViewOnTop.getBottom());
}
});
}
}
Solved....
Try this, worked for me ,
lv_chat.setAdapter(adapter);
lv_chat.setSelection(somePreviousPosition);

Animate a Fragment so that it is partially offscreen

I would like to partially hide a fragment when a new fragment appears.
I am building on ScienceGuy's code on GitHub, as referenced on Fragment Animation like Gmail Honeycomb App
I start with three fragments. The left most has a weight of 6, the middle one a weight of 4, and the right most a weight of 4. Setting the weightSum to 10 makes the left and middle fragments appear, and the right most is offscreen. This is what I want.
When the user selects an item in the middle fragment, I would then like to transition the left fragment so that two thirds of it moves off the left side of the screen. Because of the weights, I would effectively have 2, 4 , 4. At this point, the right fragment has moved onto the screen completely.
Using ScienceGuy's code, I can get the right fragment to appear when the left fragment scrolls off, but the left fragment completely disappears.
I have tried using the following code:
final float middleFragmentWidth = getMiddleFragment().getView().getWidth();
ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "x",
-middleFragmentWidth/2, 0).setDuration(
trans.getDuration(LayoutTransition.APPEARING));
trans.setAnimator(LayoutTransition.APPEARING, animIn);
ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "x", -middleFragmentWidth,
-middleFragmentWidth/2).setDuration(
trans.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
trans.setAnimator(LayoutTransition.DISAPPEARING, animOut);
I suspect that Android expects that a fragment is completely hidden when one "hides" it. Is there a way to animate fragments so that they are not actually hidden, but partially offscreen?
Alternatively, I could put the three fragments into a HorizontalScrollView, and try animating that. However, I've not done that because the middle and right most fragments contain listviews, and using a listview inside a HorizontalScrollView is to be avoided according to the docs.
I ended up creating a custom view based on LinearLayout which is 150% of the width of the device.
public class AutoSizingLinearLayout extends LinearLayout {
private static final String TAG = AutoSizingLinearLayout.class.getSimpleName();
final int myWidth;
public AutoSizingLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
myWidth = setScaledWidth(context);
}
public AutoSizingLinearLayout(Context context) {
super(context);
myWidth = setScaledWidth(context);
}
private int setScaledWidth(Context context) {
if (this.isInEditMode()){
return 1000;
} else {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return (int) (1.5 * wm.getDefaultDisplay().getWidth());
}
}
#Override
public android.view.ViewGroup.LayoutParams getLayoutParams() {
android.view.ViewGroup.LayoutParams params = super.getLayoutParams();
params.width = myWidth;
return params;
}
And then I animate it like this.
ObjectAnimator a = ObjectAnimator.ofFloat(linearView,"translationX", -scrollamount).setDuration(900);
a.start();

Categories

Resources