I'm trying to create a custom viewpager inside custom scroll viewthat dynamically wraps the current child's height.
package com.example.vihaan.dynamicviewpager;
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ScrollView;
/**
* Created by vihaan on 1/9/15.
*/
public class CustomScrollView extends ScrollView {
private GestureDetector mGestureDetector;
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, new YScrollDetector());
setFadingEdgeLength(0);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev)
&& mGestureDetector.onTouchEvent(ev);
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return (Math.abs(distanceY) > Math.abs(distanceX));
}
}
}
CustomPager
/**
* Created by vihaan on 1/9/15.
*/
public class CustomPager extends ViewPager {
public CustomPager (Context context) {
super(context);
}
public CustomPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
final View tab = getChildAt(0);
int width = getMeasuredWidth();
int tabHeight = tab.getMeasuredHeight();
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
int fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(tabHeight + fragmentHeight + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()), MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}
}
MyPagerAdapter
public class MyPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
this.fragments = new ArrayList<Fragment>();
fragments.add(new FirstFragment());
fragments.add(new SecondFragment());
fragments.add(new ThirdFragment());
fragments.add(new FourthFragment());
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
I was hoping that this would wrap around current fragments height but it is only taking the height of first child into consideration.
Sample github project : https://github.com/VihaanVerma89/DynamicViewPager
Made a few tweaks in your code and it is working fine now.
1] onMeasure function wasn't proper. Use below logic
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mCurrentView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int height = 0;
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = mCurrentView.getMeasuredHeight();
if (h > height) height = h;
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
2] ViewPager needs to be re-measured each time a page is changed. Good place to do this is setPrimaryItem function of PagerAdapter
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position != mCurrentPosition) {
Fragment fragment = (Fragment) object;
CustomPager pager = (CustomPager) container;
if (fragment != null && fragment.getView() != null) {
mCurrentPosition = position;
pager.measureCurrentView(fragment.getView());
}
}
}
Here is the link to GitHub project with these tweaks:
https://github.com/vabhishek/WrapContentViewPagerDemo
#abhishek's ans does what is required but the code below also adds animation during height change
public class WrappingViewPager extends ViewPager {
private Boolean mAnimStarted = false;
public WrappingViewPager(Context context) {
super(context);
}
public WrappingViewPager(Context context, AttributeSet attrs){
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!mAnimStarted && null != getAdapter()) {
int height = 0;
View child = ((FragmentPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView();
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
height = child.getMeasuredHeight();
if (VersionUtils.isJellyBean() && height < getMinimumHeight()) {
height = getMinimumHeight();
}
}
// Not the best place to put this animation, but it works pretty good.
int newHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
if (getLayoutParams().height != 0 && heightMeasureSpec != newHeight) {
final int targetHeight = height;
final int currentHeight = getLayoutParams().height;
final int heightChange = targetHeight - currentHeight;
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime >= 1) {
getLayoutParams().height = targetHeight;
} else {
int stepHeight = (int) (heightChange * interpolatedTime);
getLayoutParams().height = currentHeight + stepHeight;
}
requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
mAnimStarted = true;
}
#Override
public void onAnimationEnd(Animation animation) {
mAnimStarted = false;
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
a.setDuration(1000);
startAnimation(a);
mAnimStarted = true;
} else {
heightMeasureSpec = newHeight;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Just in case someone else find this post like me. Worked version without bug of initially zero height:
public class DynamicHeightViewPager extends ViewPager {
private View mCurrentView;
public DynamicHeightViewPager(Context context) {
super(context);
}
public DynamicHeightViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCurrentView != null) {
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int height = Math.max(0, mCurrentView.getMeasuredHeight());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void measureCurrentView(View currentView) {
mCurrentView = currentView;
requestLayout();
}
}
And used it in custom FragmentPagerAdapter, like this
public abstract class AutoheightFragmentPagerAdapter extends FragmentPagerAdapter {
private int mCurrentPosition = -1;
public AutoheightFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position != mCurrentPosition && container instanceof DynamicHeightViewPager) {
Fragment fragment = (Fragment) object;
DynamicHeightViewPager pager = (DynamicHeightViewPager) container;
if (fragment != null && fragment.getView() != null) {
mCurrentPosition = position;
pager.measureCurrentView(fragment.getView());
}
}
}
}
Adding to #vihaan's solution, if you have a PagerTitleStrip or PagetTabStrip, you can add this
// Account for pagerTitleStrip or pagerTabStrip
View tabStrip = getChildAt(0);
if (tabStrip instanceof PagerTitleStrip) {
tabStrip.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.UNSPECIFIED));
height += tabStrip.getMeasuredHeight();
}
just before starting the animation (before the comment
// Not the best place to put this animation, but it works pretty good.
so that the height of the strip is taken into account.
public class WrapContentViewPager extends ViewPager {
private Boolean mAnimStarted = false;
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mAnimStarted && null != getAdapter()) {
int height = 0;
View child = ((CommonViewPagerAdapter) getAdapter()).getItem(getCurrentItem()).getView();
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
height = child.getMeasuredHeight();
if (height < getMinimumHeight()) {
height = getMinimumHeight();
}
}
// Not the best place to put this animation, but it works pretty good.
int newHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
if (getLayoutParams().height != 0 && heightMeasureSpec != newHeight) {
final int targetHeight = height;
final int currentHeight = getLayoutParams().height;
final int heightChange = targetHeight - currentHeight;
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime >= 1) {
getLayoutParams().height = targetHeight;
} else {
int stepHeight = (int) (heightChange * interpolatedTime);
getLayoutParams().height = currentHeight + stepHeight;
}
requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
mAnimStarted = true;
}
#Override
public void onAnimationEnd(Animation animation) {
mAnimStarted = false;
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
a.setDuration(100);
startAnimation(a);
mAnimStarted = true;
} else {
heightMeasureSpec = newHeight;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
==============================
wrapContentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
wrapContentViewPager.measure(wrapContentViewPager.getMeasuredWidth(), wrapContentViewPager.getMeasuredHeight());
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
This few lines of code will solve the problem.
Create a custome widget for viewpager class. and in xml use it for viewpager.
public class DynamicHeightViewPager extends ViewPager {
private View mCurrentView;
public DynamicHeightViewPager(Context context) {
super(context);
}
public DynamicHeightViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCurrentView != null) {
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int height = Math.max(0, mCurrentView.getMeasuredHeight());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void measureCurrentView(View currentView) {
mCurrentView = currentView;
requestLayout();
}
}
Then in the view pager adapter override the below method and add the code.
#Override
public void setPrimaryItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
super.setPrimaryItem(container, position, object);
if (container instanceof DynamicHeightViewPager) {
// instead of card view give your root view from your item.xml file.
CardView cardView = (CardView) object;
((DynamicHeightViewPager) container).measureCurrentView(cardView);
}
}
Make your view pager height wrap_content inside xml file so you can check the result.
Related
I have a page in viewpager which have a button and a few views which are hidden by default. On click of the button, the views are to be toggled(Show/Hide). My issue is button click is working, but viewpager's height does not changes.
This is the code for adapter:
public class HomeCategoryAdapter extends PagerAdapter implements View.OnClickListener {
private LayoutInflater inflater;
private View lin_view_more, lin_view_less, lin_view_1, lin_view_2, lin_view_3, lin_view_4;
private WrapContentViewPager pager;
public HomeCategoryAdapter(Context context) {
this.mContext = context;
this.inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return 5;
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
pager = (EnhancedWrapContentViewPager) container;
View itemView = inflater.inflate(R.layout.homescreen_category_adapter, container, false);
...
lin_view_more = itemView.findViewById(R.id.lin_view_more);
lin_view_more.setOnClickListener(this);
lin_view_1 = itemView.findViewById(R.id.lin_view_1);
lin_view_1.setOnClickListener(this);
lin_view_2 = itemView.findViewById(R.id.lin_view_2);
lin_view_2.setOnClickListener(this);
lin_view_3 = itemView.findViewById(R.id.lin_view_3);
lin_view_3.setOnClickListener(this);
lin_view_4 = itemView.findViewById(R.id.lin_view_4);
lin_view_4.setOnClickListener(this);
switch (position) {
case 0:
parent_1.setVisibility(View.VISIBLE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.GONE);
break;
case 1:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.VISIBLE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.GONE);
break;
case 2:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.VISIBLE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.GONE);
break;
case 3:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.VISIBLE);
parent_5.setVisibility(View.GONE);
break;
case 4:
parent_1.setVisibility(View.GONE);
parent_2.setVisibility(View.GONE);
parent_3.setVisibility(View.GONE);
parent_4.setVisibility(View.GONE);
parent_5.setVisibility(View.VISIBLE);
break;
}
...
((WrapContentViewPager) container).addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((WrapContentViewPager) container).removeView((View) object);
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.lin_view_more:
lin_view_more.setVisibility(View.GONE);
lin_view_1.setVisibility(View.VISIBLE);
lin_view_2.setVisibility(View.VISIBLE);
lin_view_3.setVisibility(View.VISIBLE);
lin_view_4.setVisibility(View.VISIBLE);
lin_view_less.setVisibility(View.VISIBLE);
if (pager != null) {
pager.measureView();
}
break;
case R.id.lin_view_less:
lin_view_more.setVisibility(View.VISIBLE);
lin_view_1.setVisibility(View.GONE);
lin_view_2.setVisibility(View.GONE);
lin_view_3.setVisibility(View.GONE);
lin_view_4.setVisibility(View.GONE);
lin_view_less.setVisibility(View.GONE);
if (pager != null) {
pager.post(new Runnable() {
#Override
public void run() {
pager.measureView();
}
});
}
break;
}
}
}
This is the code for modified view pager to make it wrap_content:
public class WrapContentViewPager extends ViewPager {
public WrapContentViewPager (Context context) {
super(context);
}
public WrapContentViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int childMeasuredHeight = child.getMeasuredHeight();
if (childMeasuredHeight > height) {
Log.debugMessage("WrapContentViewPager", "page height updated");
height = childMeasuredHeight;
}
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void measureView(){
requestLayout();
}
}
As shown in onClick above, I am calling requestLayout() on pager. The page height gets calculated but the layout is not expanding.
Update:
If I swipe to another page(3rd or 4th) then go back to this page, show/hide are working fine.
Not getting why is it not working.
Can you add this code after requestLayout();code line:
invalidate();
if it doesn't work also can you try this:
postInvalidate();
For what these code lines do, you can take a look at this:
What does postInvalidate() do?
I would like to know if is it possible to have a PagerAdapter with wrap_content without using getChildCount() and getChildAt(i)?
I have checked the answer below, but since on my adapter doesn't have children it doesn't work.
https://stackoverflow.com/a/20784791/10020799
Does someone know how to make an wrap_content adapter in this case?
My PagerAdapter code is:
public class SectionsPagerAdapter extends PagerAdapter {
private Integer pagerTotal = 10;
public SectionsPagerAdapter(Context c) {
super();
inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setPagerTotal(Integer item) {
pagerTotal = item;
}
#Override
public int getCount() {
return pagerTotal;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.home_top_page, null);
container.addView(layout);
topMain = (ImageView) layout.findViewById(R.id.top_main);
if(imageBitmapArray != null && imageBitmapArray.length > 0){
topMain.setImageBitmap(imageBitmapArray[position]);
}else{
if(intImageArray != null) {
topMain.setImageResource(R.drawable.top_main_01);
}
}
return layout;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view.equals(object);
}
}
You can resize the ViewPager to it's current page size on page swipe from this answer.
You can use the following code:
public class WrapContentViewPager extends ViewPager {
private int mCurrentPagePosition = 0;
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
if (wrapHeight) {
View child = getChildAt(mCurrentPagePosition);
if (child != null) {
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
}
}
} catch (Exception e) {
e.printStackTrace();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void reMeasureCurrentPage(int position) {
mCurrentPagePosition = position;
requestLayout();
}
}
Use it in xml:
<your.package.name.WrapContentViewPager
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</your.package.name.WrapContentViewPager>
After that call reMeasureCurrentpage function on page swipe.
final WrapContentViewPager wrapContentViewPager = (WrapContentViewPager) findViewById(R.id.view_pager);
wrapContentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
wrapContentViewPager.reMeasureCurrentPage(wrapContentViewPager.getCurrentItem());
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Note that a list can have hundreds of items then this is not good to use this wrap_content ViewPager.
Even with a not expandable adapter is possible to use getChildCount() and getChildAt(i) functions.
Based on Khemraj's answer and this post's answer, I was able to solve the problem with the following code:
class:
public class WrapContentViewPager extends ViewPager {
public WrapContentViewPager(Context context) {
super(context);
}
public WrapContentViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
for(int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if(h > height) height = h;
}
if (height != 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
xml:
<your.package.name.WrapContentViewPager
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</your.package.name.WrapContentViewPager>
on page swipe:
final WrapContentViewPager wrapContentViewPager = (WrapContentViewPager) findViewById(R.id.view_pager);
wrapContentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
I have custom view (extends LinearLayout) which contains RecyclerView. When I add new items RecyclerView doesn't change size. I think problem is that my custom view doesn`t give enough space to RecyclerView.
The question: How to change heigh of custom view depends on chlid recylerview size?
If I'm wrong, then correct me please
CustomView:
public class ExpandableView extends LinearLayout {
private Settings mSettings ;
private int mExpandState;
private ValueAnimator mExpandAnimator;
private ValueAnimator mParentAnimator;
private AnimatorSet mExpandScrollAnimatorSet;
private int mExpandedViewHeight;
private boolean mIsInit = true;
private boolean isAllowedExpand = false;
private ScrolledParent mScrolledParent;
private OnExpandListener mOnExpandListener;
public ExpandableView(Context context) {
super(context);
init(null);
}
public ExpandableView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ExpandableView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(AttributeSet attrs) {
setOrientation(VERTICAL);
this.setClipChildren(false);
this.setClipToPadding(false);
mExpandState = ExpandState.PRE_INIT;
mSettings = new Settings();
if(attrs!=null) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableView);
mSettings.expandDuration = typedArray.getInt(R.styleable.ExpandableView_expDuration, Settings.EXPAND_DURATION);
mSettings.expandWithParentScroll = typedArray.getBoolean(R.styleable.ExpandableView_expWithParentScroll,false);
mSettings.expandScrollTogether = typedArray.getBoolean(R.styleable.ExpandableView_expExpandScrollTogether,true);
typedArray.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
if(childCount!=2) {
throw new IllegalStateException("ExpandableLayout must has two child view !");
}
if(mIsInit) {
((MarginLayoutParams)getChildAt(0).getLayoutParams()).bottomMargin=0;
MarginLayoutParams marginLayoutParams = ((MarginLayoutParams)getChildAt(1).getLayoutParams());
marginLayoutParams.bottomMargin=0;
marginLayoutParams.topMargin=0;
marginLayoutParams.height = 0;
mExpandedViewHeight = getChildAt(1).getMeasuredHeight();
mIsInit =false;
mExpandState = ExpandState.CLOSED;
View view = getChildAt(0);
if (view != null){
view.setOnClickListener(v -> toggle());
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if(mSettings.expandWithParentScroll) {
mScrolledParent = Utils.getScrolledParent(this);
}
}
private int getParentScrollDistance () {
int distance = 0;
if(mScrolledParent == null) {
return distance;
}
distance = (int) (getY() + getMeasuredHeight() + mExpandedViewHeight - mScrolledParent.scrolledView.getMeasuredHeight());
for(int index = 0; index < mScrolledParent.childBetweenParentCount; index++) {
ViewGroup parent = (ViewGroup) getParent();
distance+=parent.getY();
}
return distance;
}
private void verticalAnimate(final int startHeight, final int endHeight ) {
int distance = getParentScrollDistance();
final View target = getChildAt(1);
mExpandAnimator = ValueAnimator.ofInt(startHeight,endHeight);
mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
target.getLayoutParams().height = (int) animation.getAnimatedValue();
target.requestLayout();
}
});
mExpandAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if(endHeight-startHeight < 0) {
mExpandState = ExpandState.CLOSED;
if (mOnExpandListener != null) {
mOnExpandListener.onExpand(false);
}
} else {
mExpandState=ExpandState.EXPANDED;
if(mOnExpandListener != null) {
mOnExpandListener.onExpand(true);
}
}
}
});
//todo ??????????????????????
mExpandState=mExpandState==ExpandState.EXPANDED?ExpandState.CLOSING :ExpandState.EXPANDING;
mExpandAnimator.setDuration(mSettings.expandDuration);
if(mExpandState == ExpandState.EXPANDING && mSettings.expandWithParentScroll && distance > 0) {
mParentAnimator = Utils.createParentAnimator(mScrolledParent.scrolledView, distance, mSettings.expandDuration);
mExpandScrollAnimatorSet = new AnimatorSet();
if(mSettings.expandScrollTogether) {
mExpandScrollAnimatorSet.playTogether(mExpandAnimator,mParentAnimator);
} else {
mExpandScrollAnimatorSet.playSequentially(mExpandAnimator,mParentAnimator);
}
mExpandScrollAnimatorSet.start();
} else {
mExpandAnimator.start();
}
}
public void setExpand(boolean expand) {
if (mExpandState == ExpandState.PRE_INIT) {return;}
getChildAt(1).getLayoutParams().height=expand?mExpandedViewHeight:0;
requestLayout();
mExpandState=expand?ExpandState.EXPANDED:ExpandState.CLOSED;
}
public boolean isExpanded() {
return mExpandState==ExpandState.EXPANDED;
}
public void toggle() {
if (isAllowedExpand){
if(mExpandState==ExpandState.EXPANDED) {
close();
}else if(mExpandState==ExpandState.CLOSED) {
expand();
}
}
}
public void expand() {
verticalAnimate(0,mExpandedViewHeight);
}
public void close() {
verticalAnimate(mExpandedViewHeight,0);
}
public interface OnExpandListener {
void onExpand(boolean expanded) ;
}
public void setOnExpandListener(OnExpandListener onExpandListener) {
this.mOnExpandListener = onExpandListener;
}
public void setExpandScrollTogether(boolean expandScrollTogether) {
this.mSettings.expandScrollTogether = expandScrollTogether;
}
public void setExpandWithParentScroll(boolean expandWithParentScroll) {
this.mSettings.expandWithParentScroll = expandWithParentScroll;
}
public void setExpandDuration(int expandDuration) {
this.mSettings.expandDuration = expandDuration;
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(mExpandAnimator!=null&&mExpandAnimator.isRunning()) {
mExpandAnimator.cancel();
mExpandAnimator.removeAllUpdateListeners();
}
if(mParentAnimator!=null&&mParentAnimator.isRunning()) {
mParentAnimator.cancel();
mParentAnimator.removeAllUpdateListeners();
}
if(mExpandScrollAnimatorSet!=null) {
mExpandScrollAnimatorSet.cancel();
}
}
public void setAllowedExpand(boolean allowedExpand) {
isAllowedExpand = allowedExpand;
}
public void refreshView(){
ViewGroup.LayoutParams params = this.getLayoutParams();
Log.d("tag", "params - " + params.height);
}
}
Specify your item height inside RecyclerView Adapter like below :
LinearLayout.LayoutParams relParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
relParams.height = 100;
relParams.width = Utility.getScreenWidth(mContext);
holder.yourDesireView.setLayoutParams(relParams);
Here is the screen width calculator method :
public static int getScreenWidth(Context context) {
if (context == null) {
return 0;
}
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.widthPixels;
}
I think it will be helpful...
In this picture, you can see a CardView where each item is displayed one at a time. I have implemented a touch functionality that scrolls through the dataset. I would now like to implement the functionality of choosing an item at random. Here is where the CardView is set:
package com.chiemy.cardview.view;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ListAdapter;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
/**
* #author chiemy
*
*/
public class CardView extends FrameLayout {
private static final int ITEM_SPACE = 40;
private static final int DEF_MAX_VISIBLE = 4;
private int mMaxVisible = DEF_MAX_VISIBLE;
private int itemSpace = ITEM_SPACE;
private float mTouchSlop;
private ListAdapter mListAdapter;
private int mNextAdapterPosition;
private SparseArray<View> viewHolder = new SparseArray<View>();
private OnCardClickListener mListener;
private int topPosition;
private Rect topRect;
public interface OnCardClickListener {
void onCardClick(View view, int position);
}
public CardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public CardView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CardView(Context context) {
super(context);
init();
}
private void init() {
topRect = new Rect();
ViewConfiguration con = ViewConfiguration.get(getContext());
mTouchSlop = con.getScaledTouchSlop();
}
public void setMaxVisibleCount(int count) {
mMaxVisible = count;
}
public int getMaxVisibleCount() {
return mMaxVisible;
}
public void setItemSpace(int itemSpace) {
this.itemSpace = itemSpace;
}
public int getItemSpace() {
return itemSpace;
}
public ListAdapter getAdapter() {
return mListAdapter;
}
public void setAdapter(ListAdapter adapter) {
if (mListAdapter != null) {
mListAdapter.unregisterDataSetObserver(mDataSetObserver);
}
mNextAdapterPosition = 0;
mListAdapter = adapter;
adapter.registerDataSetObserver(mDataSetObserver);
removeAllViews();
ensureFull();
}
public void setOnCardClickListener(OnCardClickListener listener) {
mListener = listener;
}
private void ensureFull() {
while (mNextAdapterPosition < mListAdapter.getCount()
&& getChildCount() < mMaxVisible) {
int index = mNextAdapterPosition % mMaxVisible;
View convertView = viewHolder.get(index);
final View view = mListAdapter.getView(mNextAdapterPosition,
convertView, this);
view.setOnClickListener(null);
viewHolder.put(index, view);
index = Math.min(mNextAdapterPosition, mMaxVisible - 1);
ViewHelper.setScaleX(view,((mMaxVisible - index - 1) / (float) mMaxVisible) * 0.2f + 0.8f);
int topMargin = (mMaxVisible - index - 1) * itemSpace;
ViewHelper.setTranslationY(view, topMargin);
ViewHelper.setAlpha(view, mNextAdapterPosition == 0 ? 1 : 0.5f);
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (params == null) {
params = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT);
}
addViewInLayout(view, 0, params);
mNextAdapterPosition += 1;
}
// requestLayout();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
if (height > maxHeight) {
maxHeight = height;
}
if (width > maxWidth) {
maxWidth = width;
}
}
int desireWidth = widthSize;
int desireHeight = heightSize;
if (widthMode == MeasureSpec.AT_MOST) {
desireWidth = maxWidth + getPaddingLeft() + getPaddingRight();
}
if (heightMode == MeasureSpec.AT_MOST) {
desireHeight = maxHeight + (mMaxVisible - 1) * itemSpace + getPaddingTop() + getPaddingBottom();
}
setMeasuredDimension(desireWidth, desireHeight);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
View topView = getChildAt(getChildCount() - 1);
if (topView != null) {
topView.setOnClickListener(listener);
}
}
float downX, downY;
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (goDown()) {
downY = -1;
}
break;
}
return super.onTouchEvent(event);
}
/**
* 下移所有视图
*/
private boolean goDown() {
final View topView = getChildAt(getChildCount() - 1);
if(!topView.isEnabled()){
return false;
}
// topView.getHitRect(topRect); 在4.3以前有bug,用以下方法代替
topRect = getHitRect(topRect, topView);
// 如果按下的位置不在顶部视图上,则不移动
if (!topRect.contains((int) downX, (int) downY)) {
return false;
}
topView.setEnabled(false);
ViewPropertyAnimator anim = ViewPropertyAnimator
.animate(topView)
.translationY(
ViewHelper.getTranslationY(topView)
+ topView.getHeight()).alpha(0).scaleX(1)
.setListener(null).setDuration(200);
anim.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
topView.setEnabled(true);
removeView(topView);
ensureFull();
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View view = getChildAt(i);
float scaleX = ViewHelper.getScaleX(view)
+ ((float) 1 / mMaxVisible) * 0.2f;
float tranlateY = ViewHelper.getTranslationY(view)
+ itemSpace;
if (i == count - 1) {
bringToTop(view);
} else {
if ((count == mMaxVisible && i != 0)
|| count < mMaxVisible) {
ViewPropertyAnimator
.animate(view)
.translationY(tranlateY)
.setInterpolator(
new AccelerateInterpolator())
.setListener(null).scaleX(scaleX)
.setDuration(200);
}
}
}
}
});
return true;
}
private void bringToTop(final View view) {
topPosition++;
float scaleX = ViewHelper.getScaleX(view) + ((float) 1 / mMaxVisible)
* 0.2f;
float tranlateY = ViewHelper.getTranslationY(view) + itemSpace;
ViewPropertyAnimator.animate(view).translationY(tranlateY)
.scaleX(scaleX).setDuration(200).alpha(1)
.setInterpolator(new AccelerateInterpolator());
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float currentY = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float distance = currentY - downY;
if (distance > mTouchSlop) {
return true;
}
break;
}
return false;
}
public static Rect getHitRect(Rect rect, View child) {
rect.left = child.getLeft();
rect.right = child.getRight();
rect.top = (int) (child.getTop() + ViewHelper.getTranslationY(child));
rect.bottom = (int) (child.getBottom() + ViewHelper
.getTranslationY(child));
return rect;
}
private final DataSetObserver mDataSetObserver = new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
}
#Override
public void onInvalidated() {
super.onInvalidated();
}
};
private OnClickListener listener = new OnClickListener() {
#Override
public void onClick(View v) {
if (mListener != null) {
mListener.onCardClick(v, topPosition);
}
}
};
}
Here is my CardView adapter class:
package com.chiemy.cardview.view;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.chiemy.cardview.R;
public abstract class CardAdapter<T> extends BaseCardAdapter {
private final Context mContext;
private ArrayList<T> mData;
public CardAdapter(Context context) {
mContext = context;
mData = new ArrayList<T>();
}
public CardAdapter(Context context, Collection<? extends T> items) {
mContext = context;
mData = new ArrayList<T>(items);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
FrameLayout wrapper = (FrameLayout) convertView;
View cardView;
View convertedCardView;
if (wrapper == null) {
wrapper = new FrameLayout(mContext);
wrapper.setBackgroundResource(R.drawable.card_background_shadow);
cardView = getCardView(position, null, wrapper);
wrapper.addView(cardView);
} else {
cardView = wrapper.getChildAt(0);
convertedCardView = getCardView(position, cardView, wrapper);
wrapper.removeView(cardView);
wrapper.addView(convertedCardView);
if (convertedCardView != cardView) {
}
}
return wrapper;
}
protected abstract View getCardView(int position, View convertView, ViewGroup parent);
public void addAll(List<T> items){
mData.addAll(items);
}
#Override
public T getItem(int position) {
return mData.get(position);
}
#Override
public int getCount() {
return mData.size();
}
#Override
public long getItemId(int position) {
return getItem(position).hashCode();
}
public Context getContext() {
return mContext;
}
public void clear(){
if(mData != null){
mData.clear();
}
}
}
Any suggestions on how to achieve this?
To randomise an ArrayList<>, in your case, this would be the shuffling solution, you can use this code:
long seed = System.nanoTime();
Collections.shuffle(list_of_items, new Random(seed));
You will need to set the ArrayList<> back to the CardView and your items will be randomly shuffled. To retrieve a random item, visit this page to learn about getting a random item from an ArrayList<>. See how you go and ask for more help if needed.
I want a view that just like in older version of StreetView.
I have tried BottomSheetBehavior with ViewPager, but dynamic height of its fragments not working properly.
Using an CustomViewPager we can set height of fragment
public class CustomPager extends ViewPager {
private View mCurrentView;
public CustomPager(Context context) {
super(context);
}
public CustomPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mCurrentView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int height = 0;
mCurrentView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = mCurrentView.getMeasuredHeight();
if (h > height) height = h;
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void measureCurrentView(View currentView) {
mCurrentView = currentView;
requestLayout();
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}}
and add in adapter add these lines too
private int mCurrentPosition = -1;
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position != mCurrentPosition) {
Fragment fragment = (Fragment) object;
CustomPager pager = (CustomPager) container;
if (fragment != null && fragment.getView() != null) {
mCurrentPosition = position;
pager.measureCurrentView(fragment.getView());
}
}
}