Infinite looping viewpagers rely on an illusion where the count of items is increased to an arbitrarily large number, which are mapped via modulus to index positions in a list.
The problem with this being that indicators such as Circle page indicators get the arbitrarily long count of numbers, because they use the getCount() method of the host PagerAdapter, completely breaking down the illusion of infinite looping.
Ie. You have 3 items you want to loop through, you set the pageradapter count to 1000, when the user gets to item 3 and swipes to item "4", the item 1 shows again. But the indicator shows you are at item 4 and that there are hundreds of other items to swipe to. Instead of just looping between 3 indicator selections.
Is there a solution for this?
I didn't want to spend time with new libraries. So, I code a simple OnPageChangeListener for infinite viewpagers with circle indicator (ImageView). It works well. AutoScrollViewPager is used as infinite viewPager. Indicator changes when viewpager scrolled half of the page not when scrolling is over.
Screenshot (circles are images is):
Usage:
bannerPager.setOnPageChangeListener(new OnPageChangeListenerForInfiniteIndicator(getActivity(), bannerList, bannerPager.getCurrentItem()));
xml:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="#dimen/home_banner_indicator_container_height">
<LinearLayout
android:id="#+id/container_home_page_indicator"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_marginBottom="#dimen/home_banner_indicator_container_inner_margin_vertical"
android:layout_marginTop="#dimen/home_banner_indicator_container_inner_margin_vertical"
android:orientation="horizontal">
</LinearLayout>
</RelativeLayout>
The OnPageChangeListenerForInfiniteIndicator:
import android.app.Activity;
import android.support.v4.view.ViewPager;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.kartaca.rbtpicker.R;
import com.kartaca.rbtpicker.model.Banner;
import java.util.ArrayList;
import java.util.List;
/**
* Created by amadeus on 9/18/15.
*/
public class OnPageChangeListenerForInfiniteIndicator implements ViewPager.OnPageChangeListener {
private Activity activity;
private List<ImageView> pageIndicatorList = new ArrayList<ImageView>();
private List<Banner> bannerList;
private LinearLayout containerIndicator;
private int viewPagerActivePosition;
private int positionToUse = 0;
private int actualPosition;
public OnPageChangeListenerForInfiniteIndicator(Activity activity, List<Banner> bannerList, int currentItem) {
this.activity = activity;
this.bannerList = bannerList;
this.actualPosition = currentItem;
this.viewPagerActivePosition = currentItem;
loadIndicators();
}
private void loadIndicators() {
containerIndicator = (LinearLayout) activity.findViewById(R.id.container_home_page_indicator);
if (pageIndicatorList.size() < 1) {
for (Banner banner : bannerList) {
ImageView imageView = new ImageView(activity);
imageView.setImageResource(R.drawable.banner_pagination_normal);// normal indicator image
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageView.setLayoutParams(new ViewGroup.LayoutParams(activity.getResources().getDimensionPixelOffset(R.dimen.home_banner_indicator_width),
ViewGroup.LayoutParams.MATCH_PARENT));
pageIndicatorList.add(imageView);
}
}
containerIndicator.removeAllViews();
for (int x = 0; x < pageIndicatorList.size(); x++) {
ImageView imageView = pageIndicatorList.get(x);
imageView.setImageResource(x == positionToUse ? R.drawable.banner_pagination_active :
R.drawable.banner_pagination_normal); // active and notactive indicator
containerIndicator.addView(imageView);
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
actualPosition = position;
int positionToUseOld = positionToUse;
if (actualPosition < viewPagerActivePosition && positionOffset < 0.5f) {
positionToUse = actualPosition % bannerList.size();
} else {
if (positionOffset > 0.5f) {
positionToUse = (actualPosition + 1) % bannerList.size();
} else {
positionToUse = actualPosition % bannerList.size();
}
}
if (positionToUseOld != positionToUse) {
loadIndicators();
}
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
if (state == 0) {
viewPagerActivePosition = actualPosition;
positionToUse = viewPagerActivePosition % bannerList.size();
loadIndicators();
}
}
}
Take a look at InfinitePageIndicator project, maybe it is something that you need.
Related
I am very new to Android Development and for a project of mine I want to be able to have multiple animations for a fragment. The ones I want is to be able to swipe left and right and zoom in and out for each fragment. An example would be to have an Image in each fragment, and I could zoom in and out and swipe to the next image. I have seen examples online where this is achieved using a grid view. Is there any example or resources online where I can do this?
In my example I have here is that I used Viewpager 2 for the swipe animation then I attempted to implement a zoom-in feature but when I ran it overwrites the viewpager and I am unable to swipe pages only zoom in and out of that image. Can I somehow add the zoom feature in the PageTransformer class? I know this is probably a very poor way of going about it.
I know probably that in the in the PageTransformer class I need to add the zoom in and out animation how would I do that?
package com.example.viewpager;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.github.chrisbanes.photoview.PhotoView;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setPageTransformer(true, new DepthPageTransformer());
viewPager.setAdapter(new MyPagerAdapter(this));
PhotoView photoView = (PhotoView) findViewById(R.id.photo_view);
photoView.setImageResource(R.drawable.a_child_of_the_king);
photoView.setImageResource(R.drawable.a_mighty_fortress);
photoView.setImageResource(R.drawable.a_quiet_place);
photoView.setImageResource(R.drawable.all_the_way);
}
//PageTranformer class
package com.example.viewpager;
import android.support.v4.view.ViewPager;
import android.view.View;
import com.github.chrisbanes.photoview.PhotoView;
//import androidx.viewpager.widget.ViewPager;
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0f);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1f);
view.setTranslationX(0f);
view.setScaleX(1f);
view.setScaleY(1f);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0f);
}
}
}
Swiping with Viewpager makes sense in a way.
Do you load all the images into the same Layout?
Are you inflating a Fragment and inflate the single Layout onto it and then load the image into the displayed ImageView?
If so, for zooming in and out you might have to modify this approach to register touches correctly:
private ScaleGestureDetector scaleGestureDetector;
private float mScaleFactor = 1.0f;
private ImageView imageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView=findViewById(R.id.imageView);
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
}
#Override
public boolean onTouchEvent(MotionEvent motionEvent) {
scaleGestureDetector.onTouchEvent(motionEvent);
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
mScaleFactor *= scaleGestureDetector.getScaleFactor();
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
imageView.setScaleX(mScaleFactor);
imageView.setScaleY(mScaleFactor);
return true;
}
}
Happy Coding :)
Thanks, I am loading the images into the same layout. This is done in a separate class. see code below. Now The code that you provided am I able to just implement that to the Main Activity class? Would I be able to call:
viewPager.setPageTransformer(true, new DepthPageTransformer());
viewPager.setAdapter(new MyPagerAdapter(this)); as well as scaleGestureDetector function?
public class MyPagerAdapter extends PagerAdapter {
private Context mContext;
private int[] images = {
R.drawable.a_child_of_the_king,
R.drawable.a_mighty_fortress,
R.drawable.a_quiet_place,
R.drawable.all_the_way,
R.drawable.because_he_lives,
R.drawable.before_jehovahs_awful_throne,
R.drawable.before_jehovahs_awful_throne2,
R.drawable.beneath_the_cross_of_jesus
};
public MyPagerAdapter(Context context) {
mContext = context;
}
#Override
public Object instantiateItem(ViewGroup collection, int position) {
LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup viewGroup = (ViewGroup) inflater.inflate(R.layout.imagelayout,
collection, false);
ImageView imageView = (ImageView)viewGroup.findViewById(R.id.imageView);
imageView.setImageResource(images[position]);
collection.addView(viewGroup);
return viewGroup;
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
((ViewGroup) collection).removeView((View) view);
}
#Override
public int getCount() {
return images.length;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public CharSequence getPageTitle(int position) {
ModelObject customPagerEnum = ModelObject.values()[position];
return mContext.getString(customPagerEnum.getTitleResId());
}
I have a Custom ViewPager which act like a Carousel, the thing is I want to be able to show multiple page on one. To do so we have 2 solutions :
Using the margins setPageMargin() method
Or we can use Override the getPageWidth method of the adapter of my ViewPager.
The first solution is working great but I want to be able to precise exactly the percentage of my current page. The thing is with the getPageWidth solution my current page is shifting at the left. It's logical because she only takes 80% of her previous size. So I tried to override the scrollTo function of my custom ViewPager like this :
#Override
public void scrollTo(int x, int y) {
x = (int) (x - (getWidth() - getWidth()*getAdapter().getPageWidth(getCurrentItem()))/2);
super.scrollTo(x, y);
}
It's working but I can't scroll as before, it's just acting really weird, don't go in the good direction, don't follow the finger... Is there any solution which allow me to have a working scroll and a centered current page ?
Here is my adapter :
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import java.util.ArrayList;
public class CarouselAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener {
private ArrayList<Entity> mData = new ArrayList<>();
private ScaledRelativeLayout cur = null, next = null;
private float scale;
private MainActivity context;
private FragmentManager fragmentManager;
public CarouselAdapter(MainActivity context, FragmentManager fragmentManager, ArrayList<Entity> mData) {
super(fragmentManager);
this.fragmentManager = fragmentManager;
this.context = context;
this.mData = mData;
}
#Override
public float getPageWidth(int position) {
return (0.85f);
}
#Override
public Fragment getItem(int position) {
if (position == MainActivity.FIRST_PAGE) {
scale = MainActivity.BIG_SCALE;
} else {
scale = MainActivity.SMALL_SCALE;
}
position = position % mData.size();
Fragment fragment = CarouselFragment.newInstance(context, mData.get(position), position, scale);
return fragment;
}
#Override
public int getCount() {
return mData.size();
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset >= 0f && positionOffset <= 1f) {
cur = getRootView(position);
cur.setScaleBoth(MainActivity.BIG_SCALE - MainActivity.DIFF_SCALE * positionOffset);
if (position < mData.size()-1) {
next = getRootView(position +1);
next.setScaleBoth(MainActivity.SMALL_SCALE + MainActivity.DIFF_SCALE * positionOffset);
}
}
}
#Override
public void onPageSelected(int position) {}
#Override
public void onPageScrollStateChanged(int state) {}
private ScaledRelativeLayout getRootView(int position) {
return (ScaledRelativeLayout) fragmentManager.findFragmentByTag(this.getFragmentTag(position)).getView().findViewById(R.id.rootItem);
}
private String getFragmentTag(int position) {
return "android:switcher:" + context.carousel.getId() + ":" + position;
}
}
More useless but here is my MainActivity :
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import java.util.ArrayList;
public class MainActivity extends FragmentActivity {
public static int FIRST_PAGE;
public final static float BIG_SCALE = 1.0f;
public final static float SMALL_SCALE = 0.85f;
public final static float DIFF_SCALE = BIG_SCALE - SMALL_SCALE;
public CarouselViewPager carousel;
private CarouselAdapter carouselAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FIRST_PAGE = mData.size()/2;
carouselAdapter = new CarouselAdapter(this, this.getSupportFragmentManager(), mData);
carousel = (CarouselViewPager) findViewById(R.id.carousel);
carousel.setAdapter(carouselAdapter);
carousel.addOnPageChangeListener(carouselAdapter);
carousel.setCurrentItem(Math.round(FIRST_PAGE));
carousel.setOffscreenPageLimit(3);
carousel.setClipToPadding(false);
carousel.setScrollDurationFactor(2.5f);
}
}
Edit :
root.post(new Runnable() {
#Override
public void run() {
int width = root.getWidth();
Log.w("Post", "Width : " + width + ", newWidth : " + (width * 0.8));
// root.setPadding((int) (width * 0.125), 0, (int) (width * 0.125), 0);
ViewPager.LayoutParams params = (ViewPager.LayoutParams) root.getLayoutParams();
params.width = (int) (width * 0.8);
root.setLayoutParams(params);
root.requestLayout();
root.forceLayout();
root.invalidate();
}
});
Regards
Instead of setPageMargin or getPageWidth You can just set padding to your ViewPager.
<android.support.v4.view.ViewPager
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:clipToPadding="false" />
Notice the clipToPadding part, it is important to show other pages.
If you want to show your width as x% of the screen, you can do this in Java
mRootView.post(new Runnable() {
#Override
public void run() {
int w = mRootView.getWidth();
mViewPager.setPadding(w * 0.25, 0, w * 0.25, 0);
// 25% padding on either side so pages takes exactly 50% space
}
});
I implemented a collapsingtoolbar layout with a recyclerview as shown in the sample code attached. My issue is that, when I fling the list downward, it does not go all the way to the top.
What happens is that, the scrolling stops right at the point where the AppBarLayout is supposed to end.
The effect that I want is upon flinging the list downward, the list will go all the way to the top AND reveal/expand the AppBarLayout
My minSdk is 14. Any help or suggestion is greatly appreciated.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<LinearLayout
app:layout_collapseMode="parallax">
//some elements
</LinearLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
app:layout_behavior="#string/appbar_scrolling_view_behavior"/> //value android.support.design.widget.AppBarLayout$ScrollingViewBehavior
<android.support.v7.widget.Toolbar
app:popupTheme="#style/AppTheme.PopupOverlay"
app:layout_collapseMode="parallax" />
I had similar problem and I used a simple trick to expand AppBarLayout when RecyclerView fling to top (you need to have support library >= 23.x.x)
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
int firstVisiblePosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
if (firstVisiblePosition == 0) {
mAppBarLayout.setExpanded(true, true);
}
}
}
});
You can fully expand or collapse the App Bar with the setExpanded() method. One implementation could involve overriding dispatchTouchEvent() in your Activity class, and auto-collapsing/expanding your App Bar based on whether it is collapsed past the halfway point:
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
float per = Math.abs(mAppBarLayout.getY()) / mAppBarLayout.getTotalScrollRange();
boolean setExpanded = (per <= 0.5F);
mAppBarLayout.setExpanded(setExpanded, true);
}
return super.dispatchTouchEvent(event);
}
In respect to automatically scrolling to the last position on a fling, I have put some code on GitHub that shows how to programmatically smooth scroll to a specific location that may help. Calling a scroll to list.size() - 1 on a fling for instance could replicate the behaviour. Parts of this code by the way are adapted from the StylingAndroid and Novoda blogs:
public class RecyclerLayoutManager extends LinearLayoutManager {
private AppBarManager mAppBarManager;
private int visibleHeightForRecyclerView;
public RecyclerLayoutManager(Context context) {
super(context);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
View firstVisibleChild = recyclerView.getChildAt(0);
final int childHeight = firstVisibleChild.getHeight();
int distanceInPixels = ((findFirstVisibleItemPosition() - position) * childHeight);
if (distanceInPixels == 0) {
distanceInPixels = (int) Math.abs(firstVisibleChild.getY());
}
//Called Once
if (visibleHeightForRecyclerView == 0) {
visibleHeightForRecyclerView = mAppBarManager.getVisibleHeightForRecyclerViewInPx();
}
//Subtract one as adapter position 0 based
final int visibleChildCount = visibleHeightForRecyclerView/childHeight - 1;
if (position <= visibleChildCount) {
//Scroll to the very top and expand the app bar
position = 0;
mAppBarManager.expandAppBar();
} else {
mAppBarManager.collapseAppBar();
}
SmoothScroller smoothScroller = new SmoothScroller(recyclerView.getContext(), Math.abs(distanceInPixels), 1000);
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
public void setAppBarManager(AppBarManager appBarManager) {
mAppBarManager = appBarManager;
}
private class SmoothScroller extends LinearSmoothScroller {
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
private final float distanceInPixels;
private final float duration;
public SmoothScroller(Context context, int distanceInPixels, int duration) {
super(context);
this.distanceInPixels = distanceInPixels;
float millisecondsPerPx = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
this.duration = distanceInPixels < TARGET_SEEK_SCROLL_DISTANCE_PX ?
(int) (Math.abs(distanceInPixels) * millisecondsPerPx) : duration;
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return RecyclerLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int calculateTimeForScrolling(int dx) {
float proportion = (float) dx / distanceInPixels;
return (int) (duration * proportion);
}
}
}
Edit:
AppBarManager in the above code snippet refers to an interface used to communicate with the AppBarLayout in an Activity. Collapse/expand app bar methods do just that, with animations. The final method is used to calculate the number of RecyclerView rows visible on screen:
AppBarManager.java
public interface AppBarManager {
void collapseAppBar();
void expandAppBar();
int getVisibleHeightForRecyclerViewInPx();
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements AppBarManager{
#Override
public void collapseAppBar() {
mAppBarLayout.setExpanded(false, true);
}
#Override
public void expandAppBar() {
mAppBarLayout.setExpanded(true, true);
}
#Override
public int getVisibleHeightForRecyclerViewInPx() {
if (mRecyclerFragment == null) mRecyclerFragment =
(RecyclerFragment) getSupportFragmentManager().findFragmentByTag(RecyclerFragment.TAG);
int windowHeight, appBarHeight, headerViewHeight;
windowHeight = getWindow().getDecorView().getHeight();
appBarHeight = mAppBarLayout.getHeight();
headerViewHeight = mRecyclerFragment.getHeaderView().getHeight();
return windowHeight - (appBarHeight + headerViewHeight);
}
I have tried several solutions but need help. The topics below are really useful but I think I'm doing something wrong. How to set layout height/settings for both? Let's say I have 2 LinearLayout for content and bottom menu.
Also I don't want the bottom menu disappeared after sliding. It should be constant there. I am using fragments for menu clicks/change views.
Android: Expand/collapse animation
Android animate drop down/up view proper
As my comment seemed to help, I will post the link as an answer: https://github.com/umano/AndroidSlidingUpPanel
The full code cannot be pasted in StackOverflow, but the whole library will help you to achieve what you need.
The 2.2 version of the Umano Android app features a sexy sliding up
draggable panel for the currently playing article. This type of a
panel is a common pattern also used in the Google Music app and the
Rdio app. This is an open source implementation of this component that
you are free to take advantage of in your apps. Umano Team <3 Open
Source.
<com.sothree.slidinguppaneldemo.SlidingUpPanelLayout
android:id="#+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Main Content"
android:textSize="16sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|top"
android:text="The Awesome Sliding Up Panel"
android:textSize="16sp" />
</com.sothree.slidinguppaneldemo.SlidingUpPanelLayout>
You can also try this custom view for ExpandablePanel found it somewhere when i needed to create something like this.
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
public class ExpandablePanel extends LinearLayout {
private final int mHandleId;
private final int mContentId;
// Contains references to the handle and content views
private View mHandle;
private View mContent;
// Does the panel start expanded?
private boolean mExpanded = false;
// The height of the content when collapsed
private int mCollapsedHeight = 0;
// The full expanded height of the content (calculated)
private int mContentHeight = 0;
// How long the expand animation takes
private int mAnimationDuration = 0;
int height;
private Context context;
// Listener that gets fired onExpand and onCollapse
private OnExpandListener mListener;
public ExpandablePanel(Context context) {
this(context, null);
this.context = context;
}
public void setSize(int size) {
this.height = size;
}
/**
* The constructor simply validates the arguments being passed in and sets
* the global variables accordingly. Required attributes are 'handle' and
* 'content'
*/
public ExpandablePanel(Context context, AttributeSet attrs) {
super(context, attrs);
mListener = new DefaultOnExpandListener();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ExpandablePanel, 0, 0);
// How high the content should be in "collapsed" state
mCollapsedHeight = (int) a.getDimension(
R.styleable.ExpandablePanel_collapsedHeight, 0.0f);
// How long the animation should take
mAnimationDuration = a.getInteger(
R.styleable.ExpandablePanel_animationDuration, 500);
int handleId = a.getResourceId(R.styleable.ExpandablePanel_handle, 0);
if (handleId == 0) {
throw new IllegalArgumentException(
"The handle attribute is required and must refer "
+ "to a valid child.");
}
int contentId = a.getResourceId(R.styleable.ExpandablePanel_content, 0);
if (contentId == 0) {
throw new IllegalArgumentException(
"The content attribute is required and must "
+ "refer to a valid child.");
}
mHandleId = handleId;
mContentId = contentId;
a.recycle();
}
// Some public setters for manipulating the
// ExpandablePanel programmatically
public void setOnExpandListener(OnExpandListener listener) {
mListener = listener;
}
public void setCollapsedHeight(int collapsedHeight) {
mCollapsedHeight = collapsedHeight;
}
public void setAnimationDuration(int animationDuration) {
mAnimationDuration = animationDuration;
}
/**
* This method gets called when the View is physically visible to the user
*/
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mHandle = findViewById(mHandleId);
if (mHandle == null) {
throw new IllegalArgumentException(
"The handle attribute is must refer to an"
+ " existing child.");
}
mContent = findViewById(mContentId);
if (mContent == null) {
throw new IllegalArgumentException(
"The content attribute must refer to an"
+ " existing child.");
}
// This changes the height of the content such that it
// starts off collapsed
android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
lp.height = mCollapsedHeight;
mContent.setLayoutParams(lp);
// Set the OnClickListener of the handle view
mHandle.setOnClickListener(new PanelToggler());
}
/**
* This is where the magic happens for measuring the actual (un-expanded)
* height of the content. If the actual height is less than the
* collapsedHeight, the handle will be hidden.
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// First, measure how high content wants to be
mContent.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
mContentHeight = mContent.getMeasuredHeight();
Log.v("cHeight", mContentHeight + "");
Log.v("cCollapseHeight", mCollapsedHeight + "");
if (mContentHeight < mCollapsedHeight) {
mHandle.setVisibility(View.GONE);
} else {
mHandle.setVisibility(View.VISIBLE);
}
// Then let the usual thing happen
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* This is the on click listener for the handle. It basically just creates a
* new animation instance and fires animation.
*/
private class PanelToggler implements OnClickListener {
public void onClick(View v) {
Animation a;
if (mExpanded) {
a = new ExpandAnimation(mContentHeight, mCollapsedHeight);
mListener.onCollapse(mHandle, mContent);
} else {
a = new ExpandAnimation(mCollapsedHeight, mContentHeight);
mListener.onExpand(mHandle, mContent);
}
a.setDuration(mAnimationDuration);
mContent.startAnimation(a);
mExpanded = !mExpanded;
}
}
/**
* This is a private animation class that handles the expand/collapse
* animations. It uses the animationDuration attribute for the length of
* time it takes.
*/
private class ExpandAnimation extends Animation {
private final int mStartHeight;
private final int mDeltaHeight;
public ExpandAnimation(int startHeight, int endHeight) {
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
#Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
lp.height = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
mContent.setLayoutParams(lp);
}
#Override
public boolean willChangeBounds() {
return true;
}
}
/**
* Simple OnExpandListener interface
*/
public interface OnExpandListener {
public void onExpand(View handle, View content);
public void onCollapse(View handle, View content);
}
private class DefaultOnExpandListener implements OnExpandListener {
public void onCollapse(View handle, View content) {
}
public void onExpand(View handle, View content) {
}
}
}
EDIT: been working on it recently again and got it working, so don't need an answer anymore.
link
I've made an overscrollable listview and everything works except when you scroll beyond the last item with the direction pad/trackball. When scrolling with finger on screen, all the checking for top and bottom overscrolling work, even when scrolling with d-pad/trackball beyond the first item in the list... but just not the bottom side. That's why it's freaking me out, cuz the other checks all work... i'm not posting the whole activity cuz it's pretty big, i'll just post what i think is necesarry. All the checking is done in the OverscrollListview class.
Activity:
package com.somepackage;
public class SomeActivity extends ListActivity
{
private OverscrollListview mNotesList;
protected void onCreate(Bundle savedInstanceState)
{
mNotesList = (OverscrollListview) findViewById(android.R.id.list);
/* just a view in xml that can be sized so that the header and footer are always
the height of the complete screen no matter what device it runs on ...
*/
header = LayoutInflater.from(this).inflate(R.layout.listview_overscrollview, null);
header.findViewById(R.id.overscroll)
.setMinimumHeight(getWindowManager()
.getDefaultDisplay()
.getHeight());
mNotesList.addHeaderView(header, null, false);
mNotesList.addFooterView(header, null, false);
mNotesList.setOnScrollListener(mNotesList);
mNotesList.setAdapter(mainadapter);
populateNotesList();
}
...
}
public void populateNotesList()
{
...
// whenever the listview gets populated, these values need to be 0 again
mNotesList.item = mNotesList.itemOffset = 0;
}
The overscrollview class:
package com.somepackage;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Toast;
public class OverscrollListview extends ListView implements OnScrollListener
{
public int item = 0, itemOffset = 0, first = 0, count = 0, total = 0;
private int currentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
private Handler mHandler = new Handler();
private Toast toast;
private View listitem;
public OverscrollListview(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
toast = Toast.makeText(context, "", Toast.LENGTH_LONG);
}
public OverscrollListview(Context context, AttributeSet attrs)
{
super(context, attrs);
toast = Toast.makeText(context, "", Toast.LENGTH_LONG);
}
public OverscrollListview(Context context)
{
super(context);
toast = Toast.makeText(context, "", Toast.LENGTH_SHORT);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
first = firstVisibleItem;
count = visibleItemCount;
total = totalItemCount;
mHandler.postDelayed(checkListviewTopAndBottom, 100);
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState)
{
currentScrollState = scrollState;
mHandler.postDelayed(checkListviewTopAndBottom, 100);
}
private final Runnable checkListviewTopAndBottom = new Runnable()
{
#Override
public void run() {
toast.setText("first="+first+"\ncount="+count+"\nlast="+getLastVisiblePosition()+"\ntotal="+total+"\nitem="+item);
toast.show();
if ( getCount() <= 2 ) return; // do nothing, listview has no items, only header & footer
if ( currentScrollState != OnScrollListener.SCROLL_STATE_IDLE ) return; // do nothing, still scrolling
if ( getFirstVisiblePosition() < 1 ) {
setSelectionFromTop(1, getDividerHeight());
return;
}
if ( getLastVisiblePosition() == getCount()-getHeaderViewsCount() ) {
if ( item == 0 ) {
if ( getFirstVisiblePosition() < 1 ) {
item = 1;
itemOffset = getDividerHeight();
} else {
item = getCount()-getHeaderViewsCount();
listitem = getChildAt(item);
if ( listitem != null ) {
itemOffset = getHeight()-listitem.getHeight()+getDividerHeight();
} else {
itemOffset = getHeight()+getDividerHeight();
}
}
}
//toast.setText("LastVisPos()==getCount()-1\nitem="+item+"\nitemOffset="+itemOffset);
//toast.show();
if ( item == getCount()-getHeaderViewsCount() || (item == 1 && getFirstVisiblePosition() < 1) ) {
setSelectionFromTop(item, itemOffset);
}
}
}
};
}
Although the majority of it works, which i can live with, i'd appreciate if somebody could see what's going wrong cuz it's just buggin' me...
thanks.
This is not answers your main quesion, just wanted to leave some notes.
The height of the header/footer may be set in this way:
public class OverscrollListview extends ListView implements OnScrollListener
{
// ...
#Override
protected void layoutChildren() {
View v = findViewById(R.id.HEADER_VIEW_ID);
ViewGroup.LayoutParams lp = v.getLayoutParams();
lp.height = getHeight();
v.setLayoutParams(lp);
v = findViewById(R.id.FOOTER_VIEW_ID);
v.setLayoutParams(lp);
super.layoutChildren();
}
}
It seems this is more convenient way. In this way the height of header/footer can be set to the height of its LsitView.