I write a RecyclerViewRefresh follow SwipeRefreshLayout.class. When I pull the view until it doesn't move and then release,the view reset to the original. The issue is that view should trigger Timer and then that Timer reset the view. I couldn't find the reason.
Please tell me why offsetTopAndBottom() can make the view automatically back to the original place. Thanks.
I use setY() to solve this problem. But I also want to know why. And I read the offsetTopAndBottom()'s source,also can not find any clue.
RecyclerViewRefresh's code:
public class RecyclerViewRefresh extends LinearLayout {
private static final String LOG_TAG=RecyclerViewRefresh.class.getSimpleName();
private static final int INVALID_POINTER=-1;
//Default offset in dips from the top of the view to where the progress
//spinner should stop
private static final int DEFAULT_CIRCLE_TARGET=64;
private static final float DRAG_RATE=.5f;
private View headerView,footerView,thisView;
private View mTarget; //the target of the gesture
private ImageView arrowIv;
private TextView refreshTv;
private ProgressBar progressBar;
private OnPullToRefresh refreshListener=null;
private OnDragToLoad loadListener=null;
float startY=0;
private int headerHeight=0;
private boolean mReturningToStart;
private boolean mRefreshing=false;
private boolean mNestedScrollInProgress;
private int mCurrentTargetOffsetTop;
protected int mOriginalOffsetTop;
private boolean mIsBeingDragged;
private int mActivePointerId=INVALID_POINTER;
private float mInitailDownY;
private int mTouchSlop;
private float mTotalDragDistance=-1;
private float mInitialMotionY;
private float mSpinnerFinalOffset;
private boolean updateHeader=true;
private Handler handler=new Handler();
private Timer timer;
public RecyclerViewRefresh(Context context) {
super(context);
initView(context);
}
public RecyclerViewRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public RecyclerViewRefresh(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context)
{
thisView=this;
mTouchSlop= ViewConfiguration.get(context).getScaledTouchSlop();
headerView=LayoutInflater.from(context).inflate(R.layout.header_layout,null);
footerView=LayoutInflater.from(context).inflate(R.layout.header_layout,null);
measureView(headerView);
arrowIv=(ImageView)headerView.findViewById(R.id.arrow);
refreshTv=(TextView)headerView.findViewById(R.id.tip);
progressBar=(ProgressBar)headerView.findViewById(R.id.progress);
headerHeight=headerView.getMeasuredHeight();
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
headerView.getMeasuredHeight());
this.addView(headerView,lp);
setTopHeader(headerHeight);
final DisplayMetrics metrics=getResources().getDisplayMetrics();
mSpinnerFinalOffset=DEFAULT_CIRCLE_TARGET*metrics.density;
mTotalDragDistance=mSpinnerFinalOffset;
}
/**
* 通知父布局,占用的宽,高;
*
* #param view
*/
private void measureView(View view) {
ViewGroup.LayoutParams p = view.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
int height;
int tempHeight = p.height;
if (tempHeight > 0) {
height = MeasureSpec.makeMeasureSpec(tempHeight,
MeasureSpec.EXACTLY);
} else {
height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
private void setTopHeader(int height)
{
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
{
this.setY(-height);
}else{
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height);
lp.topMargin=-height;
this.setLayoutParams(lp);
}
headerView.invalidate();
}
/**
* Set the listener to be notified when a refresh is triggered via the
* pull gesture.
* #param listener
*/
public void setOnPullToRefresh(OnPullToRefresh listener)
{
this.refreshListener=listener;
}
/**
* Set the listener to be notified when a load is triggered via the
* drag gesture
* #param listener
*/
public void setOnDragToLoad(OnDragToLoad listener)
{
this.loadListener=listener;
}
private void ensureTarget(){
if(mTarget==null){
for(int i=0;i<getChildCount();i++)
{
View child=getChildAt(i);
if(child instanceof RecyclerView)
{
mTarget=child;
break;
}
}
}
}
/**
* #return Whether it is possible for the child view of this layout to
* scroll up.Override this if the child view is a custom view.
*/
public boolean canChildScrollUp(){
if(mTarget==null)
{
ensureTarget();
}
if(Build.VERSION.SDK_INT<14)
{
if(mTarget instanceof AbsListView)
{
final AbsListView absListView=(AbsListView)mTarget;
return absListView.getChildCount()>0
&&(absListView.getFirstVisiblePosition()>0
||absListView.getChildAt(0).getTop()<absListView.getPaddingTop());
}else{
return ViewCompat.canScrollVertically(mTarget,-1)|| mTarget.getScrollY()>0;
}
}else{
return ViewCompat.canScrollVertically(mTarget,-1);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
final int action=MotionEventCompat.getActionMasked(ev);
if(mReturningToStart && action == MotionEvent.ACTION_DOWN){
mReturningToStart = false;
}
if(!isEnabled() || mReturningToStart || canChildScrollUp()
||mRefreshing || mNestedScrollInProgress){
return false;
}
switch (action){
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop-headerView.getTop(),true);
mActivePointerId=MotionEventCompat.getPointerId(ev,0);
mIsBeingDragged=false;
final float initialDownY=getMotionEventY(ev,mActivePointerId);
if(initialDownY==-1){
return false;
}
mInitailDownY=initialDownY;
updateHeader=true;
break;
case MotionEvent.ACTION_MOVE:
if(mActivePointerId==INVALID_POINTER){
Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
}
final float y=getMotionEventY(ev,mActivePointerId);
if(y==-1){
return false;
}
final float yDiff=y-mInitailDownY;
if(yDiff>mTouchSlop && !mIsBeingDragged){
mInitialMotionY=mInitailDownY+mTouchSlop;
mIsBeingDragged=true;
}
break;
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged=false;
mActivePointerId=INVALID_POINTER;
break;
}
return mIsBeingDragged;
}
private float getMotionEventY(MotionEvent ev,int activePointerId){
final int index=MotionEventCompat.findPointerIndex(ev,activePointerId);
if(index<0){
return -1;
}
return MotionEventCompat.getY(ev,index);
}
private void setTargetOffsetTopAndBottom(int offset,boolean requiresUpdate){
if(this.getTop()<headerHeight+5)
{
this.offsetTopAndBottom(offset);
mCurrentTargetOffsetTop=this.getTop();
if(requiresUpdate && Build.VERSION.SDK_INT<11){
invalidate();
}
if(this.getTop()>headerHeight)
{
if(updateHeader){
updateHeader=false;
refreshTv.setText(getResources().getText(R.string.releasetorefresh));
RotateAnimation animation=new RotateAnimation(0,180,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(800);
animation.setFillAfter(true);
arrowIv.startAnimation(animation);
}
}
}
}
private void onSecondaryPointerUp(MotionEvent ev){
final int pointerIndex=MotionEventCompat.getActionIndex(ev);
final int pointerId=MotionEventCompat.getPointerId(ev,pointerIndex);
if(pointerId==mActivePointerId){
//This was our active pointer going up. Choose a new
//active pointer and adjust accordingly.
final int newPointerIndex=pointerIndex==0?1:0;
mActivePointerId=MotionEventCompat.getPointerId(ev,newPointerIndex);
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
final int action=MotionEventCompat.getActionMasked(event);
int pointerIndex=-1;
if(mReturningToStart&&action==MotionEvent.ACTION_DOWN){
mReturningToStart=false;
}
if(!isEnabled() || mReturningToStart
|| canChildScrollUp() || mNestedScrollInProgress){
//Fail fast if we're not in a state where a swipe is possible
return false;
}
switch(action){
case MotionEvent.ACTION_DOWN:
mActivePointerId=MotionEventCompat.getPointerId(event,0);
mIsBeingDragged=false;
break;
case MotionEvent.ACTION_MOVE:{
pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
return false;
}
final float y=MotionEventCompat.getY(event,pointerIndex);
final float overscrollTop=(y-mInitialMotionY)*DRAG_RATE;
if(mIsBeingDragged){
if(overscrollTop>0){
moveSpinner(overscrollTop);
}else{
return false;
}
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN:{
pointerIndex=MotionEventCompat.getActionIndex(event);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
return false;
}
mActivePointerId=MotionEventCompat.getPointerId(event,pointerIndex);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(event);
break;
case MotionEvent.ACTION_UP:{
pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
return false;
}
final float y=MotionEventCompat.getY(event,pointerIndex);
mIsBeingDragged=false;
finishSpinner();
mActivePointerId=INVALID_POINTER;
return false;
}
case MotionEvent.ACTION_CANCEL:
return false;
}
return true;
}
private void moveSpinner(float overscrollTop){
float originalDragPercent=overscrollTop/mTotalDragDistance;
float dragPercent=Math.min(1f,Math.abs(originalDragPercent));
float adjustedPercent=(float)Math.max(dragPercent-.4,0)*5/3;
float extraOS=Math.abs(overscrollTop)-mTotalDragDistance;
float slingshotDist=mSpinnerFinalOffset;
float tensionSlingshotPercent=Math.max(0,Math.min(extraOS,slingshotDist*2)/slingshotDist);
float tensionPercent=(float)((tensionSlingshotPercent/4)-Math.pow(
(tensionSlingshotPercent/4),2))*2f;
float extraMove=(slingshotDist)*tensionPercent*2;
int targetY=mOriginalOffsetTop+(int)((slingshotDist*dragPercent)+extraMove);
setTargetOffsetTopAndBottom(targetY-mCurrentTargetOffsetTop,true);
}
private void finishSpinner(){
if(this.getTop()>headerHeight){
setRefreshing(true,true);
}else{
//cancel refresh
mRefreshing=false;
animateOffsetToStartPosition();
}
}
private void setRefreshing(boolean refreshing,final boolean notify)
{
if(mRefreshing!=refreshing){
ensureTarget();
mRefreshing=refreshing;
if(mRefreshing){
refreshListener.onRefresh();
arrowIv.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}else{
arrowIv.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
animateOffsetToStartPosition();
}
}
}
public void setRefreshing(boolean refreshing){
if(!refreshing){
setRefreshing(refreshing,false);
}
}
private void animateOffsetToStartPosition(){
refreshTv.setText(getResources().getText(R.string.pulltorefresh));
arrowIv.clearAnimation();
Log.d(LOG_TAG,"getTop="+this.getTop()+" timer="+((timer==null)?"null":"notnumm"));
if(timer==null&&this.getTop()>0)
{
timer=new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
if(thisView.getTop()>0)
{
thisView.offsetTopAndBottom(-1);
mCurrentTargetOffsetTop = headerView.getTop();
if ( Build.VERSION.SDK_INT < 11) {
invalidate();
}
}else{
Log.d(LOG_TAG,"cancel");
timer.cancel();
timer=null;
}
}
});
}
},10,10);
}
}
/**
* Classes that wish to be notified when the pull gesture correctly
* triggers a refresh should implement this interface.
*/
public interface OnPullToRefresh{
public void onRefresh();
}
/**
* Classes that wish to be notified when the drag gesture correctly
* triggers a load should implement this interface.
*/
public interface OnDragToLoad{
public void onLoad();
}}
offsetTopAndBottom(offset) will add mTop and mBottom of View by offset.
private void animateOffsetToStartPosition(){
refreshTv.setText(getResources().getText(R.string.pulltorefresh));
arrowIv.clearAnimation();
Log.d(LOG_TAG,"getTop="+this.getTop()+" timer="+((timer==null)?"null":"notnumm"));
if(timer==null&&this.getTop()>0)
{
timer=new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
if(thisView.getTop()>0)
{
//this line says that if top of thisView is not 0,add mtop and mBottom of thisView by -1
//this timer will change the mTop to 0.thisView will be back to original place if mTop == 0.
thisView.offsetTopAndBottom(-1);
mCurrentTargetOffsetTop = headerView.getTop();
if ( Build.VERSION.SDK_INT < 11) {
invalidate();
}
}else{
Log.d(LOG_TAG,"cancel");
timer.cancel();
timer=null;
}
}
});
}
},10,10);
}
}
setY() calls setTranslationY(), which makes two calls to invalidateViewProperty(boolean invalidateParent, boolean forceRedraw).
In setTranslationY(), when it calls invalidateViewProperty, it is passing forceRedraw as true, which redraws the view and puts it back to the original state.
Related
I have a RecyclerView with a HORIZONTAL orientation. I want to scroll RecyclerView automatically from Right to Left.
also, I need below options
should scroll in an infinite loop
support touch while auto-scroll
support reverse auto-scroll (Left to Right)
should work in all Layout manager (LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager)
Finally, I found a solution to the above question with all the options.
For start Autoscrolling
recyclerView.startAutoScroll();
For infinite loop
recyclerView.setLoopEnabled(true);
For touch
recyclerView.setCanTouch(true);
For pause Autoscrolling
recyclerView.pauseAutoScroll(true);
It will work with all Layout manager (LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager)
AutoScrollRecyclerView
public class AutoScrollRecyclerView extends RecyclerView {
private static final String TAG = AutoScrollRecyclerView.class.getSimpleName();
private static final int SPEED = 10;
/**
* Sliding estimator
*/
private UniformSpeedInterpolator mInterpolator;
/**
* Dx and dy between units
*/
private int mSpeedDx, mSpeedDy;
/**
* Sliding speed, default 100
*/
private int mCurrentSpeed = SPEED;
/**
* Whether to display the list infinitely
*/
private boolean mLoopEnabled;
/**
* Whether to slide backwards
*/
private boolean mReverse;
/**
* Whether to turn on automatic sliding
*/
private boolean mIsOpenAuto;
/**
* Whether the user can manually slide the screen
*/
private boolean mCanTouch = true;
/**
* Whether the user clicks on the screen
*/
private boolean mPointTouch;
/**
* Are you ready for data?
*/
private boolean mReady;
/**
* Whether initialization is complete
*/
private boolean mInflate;
/**
* Whether to stop scroll
*/
private boolean isStopAutoScroll = false;
public AutoScrollRecyclerView(Context context) {
this(context, null);
}
public AutoScrollRecyclerView(Context context, #Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoScrollRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mInterpolator = new UniformSpeedInterpolator();
mReady = false;
}
/**
* Start sliding
*/
public void startAutoScroll() {
isStopAutoScroll = false;
openAutoScroll(mCurrentSpeed, false);
}
/**
* Start sliding
*
* #param speed Sliding distance (determining the sliding speed)
* #param reverse Whether to slide backwards
*/
public void openAutoScroll(int speed, boolean reverse) {
mReverse = reverse;
mCurrentSpeed = speed;
mIsOpenAuto = true;
notifyLayoutManager();
startScroll();
}
/**
* Is it possible to manually slide when swiping automatically?
*/
public void setCanTouch(boolean b) {
mCanTouch = b;
}
public boolean canTouch() {
return mCanTouch;
}
/**
* Set whether to display the list infinitely
*/
public void setLoopEnabled(boolean loopEnabled) {
this.mLoopEnabled = loopEnabled;
if (getAdapter() != null) {
getAdapter().notifyDataSetChanged();
startScroll();
}
}
/**
* Whether to slide infinitely
*/
public boolean isLoopEnabled() {
return mLoopEnabled;
}
/**
* Set whether to reverse
*/
public void setReverse(boolean reverse) {
mReverse = reverse;
notifyLayoutManager();
startScroll();
}
/**
* #param isStopAutoScroll
*/
public void pauseAutoScroll(boolean isStopAutoScroll) {
this.isStopAutoScroll = isStopAutoScroll;
}
public boolean getReverse() {
return mReverse;
}
/**
* Start scrolling
*/
private void startScroll() {
if (!mIsOpenAuto)
return;
if (getScrollState() == SCROLL_STATE_SETTLING)
return;
if (mInflate && mReady) {
mSpeedDx = mSpeedDy = 0;
smoothScroll();
}
}
private void smoothScroll() {
if (!isStopAutoScroll) {
int absSpeed = Math.abs(mCurrentSpeed);
int d = mReverse ? -absSpeed : absSpeed;
smoothScrollBy(d, d, mInterpolator);
}
}
private void notifyLayoutManager() {
LayoutManager layoutManager = getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = ((LinearLayoutManager) layoutManager);
if (linearLayoutManager != null) {
linearLayoutManager.setReverseLayout(mReverse);
}
} else {
StaggeredGridLayoutManager staggeredGridLayoutManager = ((StaggeredGridLayoutManager) layoutManager);
if (staggeredGridLayoutManager != null) {
staggeredGridLayoutManager.setReverseLayout(mReverse);
}
}
}
#Override
public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
super.swapAdapter(generateAdapter(adapter), removeAndRecycleExistingViews);
mReady = true;
}
#Override
public void setAdapter(Adapter adapter) {
super.setAdapter(generateAdapter(adapter));
mReady = true;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mCanTouch) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mPointTouch = true;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mIsOpenAuto) {
return true;
}
}
return super.onInterceptTouchEvent(e);
} else return true;
}
#Override
public boolean onTouchEvent(MotionEvent e) {
if (mCanTouch) {
switch (e.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mIsOpenAuto) {
mPointTouch = false;
smoothScroll();
return true;
}
}
return super.onTouchEvent(e);
} else return true;
}
#Override
public boolean performClick() {
return super.performClick();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
startScroll();
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mInflate = true;
}
#Override
public void onScrolled(int dx, int dy) {
if (mPointTouch) {
mSpeedDx = 0;
mSpeedDy = 0;
return;
}
boolean vertical;
if (dx == 0) {//Vertical scrolling
mSpeedDy += dy;
vertical = true;
} else {//Horizontal scrolling
mSpeedDx += dx;
vertical = false;
}
if (vertical) {
if (Math.abs(mSpeedDy) >= Math.abs(mCurrentSpeed)) {
mSpeedDy = 0;
smoothScroll();
}
} else {
if (Math.abs(mSpeedDx) >= Math.abs(mCurrentSpeed)) {
mSpeedDx = 0;
smoothScroll();
}
}
}
#NonNull
#SuppressWarnings("unchecked")
private NestingRecyclerViewAdapter generateAdapter(Adapter adapter) {
return new NestingRecyclerViewAdapter(this, adapter);
}
/**
* Custom estimator
* Swipe the list at a constant speed
*/
private static class UniformSpeedInterpolator implements Interpolator {
#Override
public float getInterpolation(float input) {
return input;
}
}
/**
* Customize the Adapter container so that the list can be displayed in an infinite loop
*/
private static class NestingRecyclerViewAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
private AutoScrollRecyclerView mRecyclerView;
RecyclerView.Adapter<VH> mAdapter;
NestingRecyclerViewAdapter(AutoScrollRecyclerView recyclerView, RecyclerView.Adapter<VH> adapter) {
mAdapter = adapter;
mRecyclerView = recyclerView;
}
#NonNull
#Override
public VH onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return mAdapter.onCreateViewHolder(parent, viewType);
}
#Override
public void registerAdapterDataObserver(#NonNull RecyclerView.AdapterDataObserver observer) {
super.registerAdapterDataObserver(observer);
mAdapter.registerAdapterDataObserver(observer);
}
#Override
public void unregisterAdapterDataObserver(#NonNull RecyclerView.AdapterDataObserver observer) {
super.unregisterAdapterDataObserver(observer);
mAdapter.unregisterAdapterDataObserver(observer);
}
#Override
public void onBindViewHolder(#NonNull VH holder, int position) {
mAdapter.onBindViewHolder(holder, generatePosition(position));
}
#Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(hasStableIds);
mAdapter.setHasStableIds(hasStableIds);
}
#Override
public int getItemCount() {
//If it is an infinite scroll mode, set an unlimited number of items
return getLoopEnable() ? Integer.MAX_VALUE : mAdapter.getItemCount();
}
#Override
public int getItemViewType(int position) {
return mAdapter.getItemViewType(generatePosition(position));
}
#Override
public long getItemId(int position) {
return mAdapter.getItemId(generatePosition(position));
}
/**
* Returns the corresponding position according to the current scroll mode
*/
private int generatePosition(int position) {
if (getLoopEnable()) {
return getActualPosition(position);
} else {
return position;
}
}
/**
* Returns the actual position of the item
*
* #param position The position after starting to scroll will grow indefinitely
* #return Item actual location
*/
private int getActualPosition(int position) {
int itemCount = mAdapter.getItemCount();
return position >= itemCount ? position % itemCount : position;
}
private boolean getLoopEnable() {
return mRecyclerView.mLoopEnabled;
}
public boolean getReverse() {
return mRecyclerView.mReverse;
}
}
}
I put addView() in onTouchEvent() method,but it doesn't work. What's the problem? When I put the addView in the construction function it works properly.
onTouchEvent() works properly. The main problem is the new addview doesn't visible. But it really added to the parent view.
setOrientation(LinearLayout.VERTICAL) is already added in the construction function.
This is the code:
public class RecyclerViewRefresh extends LinearLayout{
private static final String LOG_TAG=RecyclerViewRefresh.class.getSimpleName();
private static final int INVALID_POINTER=-1;
//Default offset in dips from the top of the view to where the progress
//spinner should stop
private static final int DEFAULT_CIRCLE_TARGET=64;
private static final float DRAG_RATE=.5f;
private Context context;
private View headerView,footerView,thisView;
private View mTarget; //the target of the gesture
private ImageView arrowIv;
private TextView refreshTv;
private ProgressBar progressBar,footerProgressBar;
private OnPullToRefresh refreshListener=null;
private OnDragToLoad loadListener=null;
float startY=0;
private int headerHeight=0,currentHeaderHeight=0,currentFooterHeight=0;
private boolean mReturningToStart;
private boolean mRefreshing=false;
private boolean mNestedScrollInProgress;
private int mCurrentTargetOffsetTop;
protected int mOriginalOffsetTop;
private boolean mIsBeingDragged;
private boolean mIsBeingPullUp;
private boolean isAddFooter=false;
private int mActivePointerId=INVALID_POINTER;
private float mInitailDownY;
private int mTouchSlop;
private float mTotalDragDistance=-1;
private float mInitialMotionY;
private float mSpinnerFinalOffset;
private boolean updateHeader=true;
private Handler handler=new Handler();
private Timer timer;
public RecyclerViewRefresh(Context context) {
super(context);
initView(context);
}
public RecyclerViewRefresh(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public RecyclerViewRefresh(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context)
{
this.setOrientation(LinearLayout.VERTICAL);
this.context=context;
thisView=this;
mTouchSlop= ViewConfiguration.get(context).getScaledTouchSlop();
headerView=LayoutInflater.from(context).inflate(R.layout.header_layout,null);
footerView=LayoutInflater.from(context).inflate(R.layout.footer_layout,null);
measureView(headerView);
measureView(footerView);
arrowIv=(ImageView)headerView.findViewById(R.id.arrow);
refreshTv=(TextView)headerView.findViewById(R.id.tip);
progressBar=(ProgressBar)headerView.findViewById(R.id.progress);
headerHeight=headerView.getMeasuredHeight();
currentHeaderHeight=headerHeight;
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
headerView.getMeasuredHeight());
this.addView(headerView,lp);
setTopHeader(headerHeight);
final DisplayMetrics metrics=getResources().getDisplayMetrics();
mSpinnerFinalOffset=DEFAULT_CIRCLE_TARGET*metrics.density;
mTotalDragDistance=mSpinnerFinalOffset;
}
/**
* 通知父布局,占用的宽,高;
*
* #param view
*/
private void measureView(View view) {
ViewGroup.LayoutParams p = view.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
int height;
int tempHeight = p.height;
if (tempHeight > 0) {
height = MeasureSpec.makeMeasureSpec(tempHeight,
MeasureSpec.EXACTLY);
} else {
height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
private void setTopHeader(int height)
{
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
{
this.setY(-height);
}else{
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height);
lp.topMargin=-height;
this.setLayoutParams(lp);
}
headerView.invalidate();
}
/**
* Set the listener to be notified when a refresh is triggered via the
* pull gesture.
* #param listener
*/
public void setOnPullToRefresh(OnPullToRefresh listener)
{
this.refreshListener=listener;
}
/**
* Set the listener to be notified when a load is triggered via the
* drag gesture
* #param listener
*/
public void setOnDragToLoad(OnDragToLoad listener)
{
this.loadListener=listener;
}
private void ensureTarget(){
if(mTarget==null){
for(int i=0;i<getChildCount();i++)
{
View child=getChildAt(i);
if(child instanceof RecyclerView)
{
mTarget=child;
break;
}
}
}
}
/**
* #return Whether it is possible for the child view of this layout to
* scroll up.Override this if the child view is a custom view.
*/
public boolean canChildScrollUp(){
if(mTarget==null)
{
ensureTarget();
}
if(Build.VERSION.SDK_INT<14)
{
if(mTarget instanceof AbsListView)
{
final AbsListView absListView=(AbsListView)mTarget;
return absListView.getChildCount()>0
&&(absListView.getFirstVisiblePosition()>0
||absListView.getChildAt(0).getTop()<absListView.getPaddingTop());
}else{
return ViewCompat.canScrollVertically(mTarget,-1)|| mTarget.getScrollY()>0;
}
}else{
return ViewCompat.canScrollVertically(mTarget,-1);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
final int action=MotionEventCompat.getActionMasked(ev);
if(mReturningToStart && action == MotionEvent.ACTION_DOWN){
mReturningToStart = false;
}
if(!isEnabled() || mReturningToStart || (canChildScrollUp()&&!ifLastItemVisible())
||mRefreshing || mNestedScrollInProgress){
return false;
}
switch (action){
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop-headerView.getTop(),true);
mActivePointerId=MotionEventCompat.getPointerId(ev,0);
mIsBeingDragged=false;
mIsBeingPullUp=false;
final float initialDownY=getMotionEventY(ev,mActivePointerId);
if(initialDownY==-1){
return false;
}
mInitailDownY=initialDownY;
updateHeader=true;
break;
case MotionEvent.ACTION_MOVE:
if(mActivePointerId==INVALID_POINTER){
Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
}
final float y=getMotionEventY(ev,mActivePointerId);
if(y==-1){
return false;
}
final float yDiff=y-mInitailDownY;
if(yDiff>mTouchSlop && !mIsBeingDragged){
mInitialMotionY=mInitailDownY+mTouchSlop;
mIsBeingDragged=true;
}
if(yDiff<-mTouchSlop&&!mIsBeingPullUp&&ifLastItemVisible()&&!isAddFooter)
{
Log.d(LOG_TAG,"pullUp");
mInitialMotionY=mInitailDownY+mTouchSlop;
mIsBeingPullUp=true;
return true;
}
if(ifLastItemVisible()&&yDiff>mTouchSlop)
{
isAddFooter=false;
return false;
}
break;
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged=false;
mActivePointerId=INVALID_POINTER;
break;
}
return mIsBeingDragged;
}
private boolean ifLastItemVisible()
{
final RecyclerView recyclerView=(RecyclerView)mTarget;
LinearLayoutManager manager=(LinearLayoutManager)recyclerView.getLayoutManager();
if((manager.findLastVisibleItemPosition()+1)==manager.getItemCount())
{
return true;
}
return false;
}
private float getMotionEventY(MotionEvent ev,int activePointerId){
final int index=MotionEventCompat.findPointerIndex(ev,activePointerId);
if(index<0){
return -1;
}
return MotionEventCompat.getY(ev,index);
}
private void setTargetOffsetTopAndBottom(int offset,boolean requiresUpdate){
if(currentHeaderHeight>-5)
{
currentHeaderHeight-=offset;
this.setY(-currentHeaderHeight);
mCurrentTargetOffsetTop=this.getTop();
if(requiresUpdate && Build.VERSION.SDK_INT<11){
invalidate();
}
if(currentHeaderHeight<0)
{
if(updateHeader){
updateHeader=false;
refreshTv.setText(getResources().getText(R.string.releasetorefresh));
RotateAnimation animation=new RotateAnimation(0,180,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(800);
animation.setFillAfter(true);
arrowIv.startAnimation(animation);
}
Log.d(LOG_TAG,"top="+this.getY());
}
}
}
private void onSecondaryPointerUp(MotionEvent ev){
final int pointerIndex=MotionEventCompat.getActionIndex(ev);
final int pointerId=MotionEventCompat.getPointerId(ev,pointerIndex);
if(pointerId==mActivePointerId){
//This was our active pointer going up. Choose a new
//active pointer and adjust accordingly.
final int newPointerIndex=pointerIndex==0?1:0;
mActivePointerId=MotionEventCompat.getPointerId(ev,newPointerIndex);
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
final int action=MotionEventCompat.getActionMasked(event);
int pointerIndex=-1;
if(mReturningToStart&&action==MotionEvent.ACTION_DOWN){
mReturningToStart=false;
}
if(!isEnabled() || mReturningToStart
|| (canChildScrollUp()&&!ifLastItemVisible()) || mNestedScrollInProgress){
//Fail fast if we're not in a state where a swipe is possible
return false;
}
switch(action){
case MotionEvent.ACTION_DOWN:
mActivePointerId=MotionEventCompat.getPointerId(event,0);
mIsBeingDragged=false;
break;
case MotionEvent.ACTION_MOVE:{
pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
return false;
}
final float y=MotionEventCompat.getY(event,pointerIndex);
final float overscrollTop=(y-mInitialMotionY)*DRAG_RATE;
Log.d(LOG_TAG,"move overscroll="+overscrollTop+" y="+y+" mInitialMotionY="+mInitialMotionY+" mIsBeingDragged="+(mIsBeingDragged?"true":"false")
+" mIsBeingPullUp="+(mIsBeingPullUp?"true":"false"));
if(mIsBeingDragged){
if(overscrollTop>0){
moveSpinner(overscrollTop);
}else{
return false;
}
}
if(mIsBeingPullUp&&!isAddFooter){
Log.d(LOG_TAG,"isAddFooter="+(isAddFooter?"true":"false"));
if(overscrollTop<0&&!isAddFooter){
isAddFooter=true;
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
footerView.getMeasuredHeight());
Log.d(LOG_TAG,"addFooterView height="+footerView.getMeasuredHeight()
+" childcount="+this.getChildCount());
if(this.getChildCount()==2)
{
this.addView(footerView,lp);
footerView.setVisibility(View.VISIBLE);
invalidate();
}
Log.d(LOG_TAG,"childcount="+this.getChildCount());
}else{
return false;
}
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN:{
pointerIndex=MotionEventCompat.getActionIndex(event);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
return false;
}
mActivePointerId=MotionEventCompat.getPointerId(event,pointerIndex);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(event);
break;
case MotionEvent.ACTION_UP:{
pointerIndex=MotionEventCompat.findPointerIndex(event,mActivePointerId);
if(pointerIndex<0){
Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
return false;
}
final float y=MotionEventCompat.getY(event,pointerIndex);
mIsBeingDragged=false;
finishSpinner();
mActivePointerId=INVALID_POINTER;
return false;
}
case MotionEvent.ACTION_CANCEL:
return false;
}
return true;
}
private void moveSpinner(float overscrollTop){
float originalDragPercent=overscrollTop/mTotalDragDistance;
float dragPercent=Math.min(1f,Math.abs(originalDragPercent));
float adjustedPercent=(float)Math.max(dragPercent-.4,0)*5/3;
float extraOS=Math.abs(overscrollTop)-mTotalDragDistance;
float slingshotDist=mSpinnerFinalOffset;
float tensionSlingshotPercent=Math.max(0,Math.min(extraOS,slingshotDist*2)/slingshotDist);
float tensionPercent=(float)((tensionSlingshotPercent/4)-Math.pow(
(tensionSlingshotPercent/4),2))*2f;
float extraMove=(slingshotDist)*tensionPercent*2;
int targetY=mOriginalOffsetTop+(int)((slingshotDist*dragPercent)+extraMove);
setTargetOffsetTopAndBottom(targetY-mCurrentTargetOffsetTop,true);
}
private void finishSpinner(){
if(currentHeaderHeight<0){
setRefreshing(true,true);
}else{
//cancel refresh
mRefreshing=false;
animateOffsetToStartPosition();
}
}
private void setRefreshing(boolean refreshing,final boolean notify)
{
if(mRefreshing!=refreshing){
ensureTarget();
mRefreshing=refreshing;
if(mRefreshing){
refreshListener.onRefresh();
arrowIv.setVisibility(View.GONE);
arrowIv.clearAnimation();
progressBar.setVisibility(View.VISIBLE);
}else{
arrowIv.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
refreshTv.setText(getResources().getText(R.string.afterrefresh));
animateOffsetToStartPosition();
}
}
}
public void setRefreshing(boolean refreshing){
if(!refreshing){
setRefreshing(refreshing,false);
}
}
private void animateOffsetToStartPosition(){
refreshTv.setText(getResources().getText(R.string.pulltorefresh));
arrowIv.clearAnimation();
if(timer==null&¤tHeaderHeight<headerHeight)
{
timer=new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
handler.post(new Runnable() {
#Override
public void run() {
if(currentHeaderHeight<headerHeight)
{
currentHeaderHeight+=5;
thisView.setY(-currentHeaderHeight);
mCurrentTargetOffsetTop = headerView.getTop();
if ( Build.VERSION.SDK_INT < 11) {
invalidate();
}
}else{
if(timer!=null)
{
arrowIv.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
timer.cancel();
timer=null;
}
}
}
});
}
},10,10);
}
}
/**
* Classes that wish to be notified when the pull gesture correctly
* triggers a refresh should implement this interface.
*/
public interface OnPullToRefresh{
public void onRefresh();
}
/**
* Classes that wish to be notified when the drag gesture correctly
* triggers a load should implement this interface.
*/
public interface OnDragToLoad{
public void onLoad();
}}
If you're sure the view is added but it's not visible, make sure you have set the orientation of your LinearLayout. The default orientation is horizontal and maybe the view is added to the side of your existing views and is not visible. You can set it to vertical orientation by using:
myLayout.setOrientation(LinearLayout.VERTICAL);
Put your linear layout inside scroll view.
You need to implement View.OnTouchListener. Just modify your code like below :
public class RecyclerViewRefresh extends LinearLayout implements View.OnTouchListener
{
#Override
public boolean onTouchEvent(MotionEvent event)
{
......
measureView(footerView);
footerView.setVisibility(View.VISIBLE);
thisView.addView(footerView,lp);
invalidate();
.....
}
}
Try calling invalidate(); after addView();
I am using swipe refresh layout but it is not working for web view. It is working for a simple activity layout, but when i am using a web view it's not working properly.
Here is my code.
public class SwipeActivity extends Activity implements OnRefreshListener{
SwipeRefreshLayout swipeLayout;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swipe);
swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
swipeLayout.setOnRefreshListener(this);
swipeLayout.setColorScheme(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
}
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
#Override public void run() {
swipeLayout.setRefreshing(false);
}
}, 5000);
}
}
And this one is swipe refresh layout.
public class SwipeRefreshLayout extends ViewGroup {
private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300;
private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f;
private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
private static final float PROGRESS_BAR_HEIGHT = 4;
private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f;
private static final int REFRESH_TRIGGER_DISTANCE = 120;
private SwipeProgressBar mProgressBar; //the thing that shows progress is going
private View mTarget; //the content that gets pulled down
private int mOriginalOffsetTop;
private OnRefreshListener mListener;
private MotionEvent mDownEvent;
private int mFrom;
private boolean mRefreshing = false;
private int mTouchSlop;
private float mDistanceToTriggerSync = -1;
private float mPrevY;
private int mMediumAnimationDuration;
private float mFromPercentage = 0;
private float mCurrPercentage = 0;
private int mProgressBarHeight;
private int mCurrentTargetOffsetTop;
// Target is returning to its start offset because it was cancelled or a
// refresh was triggered.
private boolean mReturningToStart;
private final DecelerateInterpolator mDecelerateInterpolator;
private final AccelerateInterpolator mAccelerateInterpolator;
private static final int[] LAYOUT_ATTRS = new int[] {
android.R.attr.enabled
};
private final Animation mAnimateToStartPosition = new Animation() {
#Override
public void applyTransformation(float interpolatedTime, Transformation t) {
int targetTop = 0;
if (mFrom != mOriginalOffsetTop) {
targetTop = (mFrom + (int)((mOriginalOffsetTop - mFrom) * interpolatedTime));
}
int offset = targetTop - mTarget.getTop();
final int currentTop = mTarget.getTop();
if (offset + currentTop < 0) {
offset = 0 - currentTop;
}
setTargetOffsetTopAndBottom(offset);
}
};
private Animation mShrinkTrigger = new Animation() {
#Override
public void applyTransformation(float interpolatedTime, Transformation t) {
float percent = mFromPercentage + ((0 - mFromPercentage) * interpolatedTime);
mProgressBar.setTriggerPercentage(percent);
}
};
private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
// Once the target content has returned to its start position, reset
// the target offset to 0
mCurrentTargetOffsetTop = 0;
}
};
private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
mCurrPercentage = 0;
}
};
private final Runnable mReturnToStartPosition = new Runnable() {
#Override
public void run() {
mReturningToStart = true;
animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),
mReturnToStartPositionListener);
}
};
// Cancel the refresh gesture and animate everything back to its original state.
private final Runnable mCancel = new Runnable() {
#Override
public void run() {
mReturningToStart = true;
// Timeout fired since the user last moved their finger; animate the
// trigger to 0 and put the target back at its original position
if (mProgressBar != null) {
mFromPercentage = mCurrPercentage;
mShrinkTrigger.setDuration(mMediumAnimationDuration);
mShrinkTrigger.setAnimationListener(mShrinkAnimationListener);
mShrinkTrigger.reset();
mShrinkTrigger.setInterpolator(mDecelerateInterpolator);
startAnimation(mShrinkTrigger);
}
animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),
mReturnToStartPositionListener);
}
};
/**
* Simple constructor to use when creating a SwipeRefreshLayout from code.
* #param context
*/
public SwipeRefreshLayout(Context context) {
this(context, null);
}
/**
* Constructor that is called when inflating SwipeRefreshLayout from XML.
* #param context
* #param attrs
*/
public SwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMediumAnimationDuration = getResources().getInteger(
android.R.integer.config_mediumAnimTime);
setWillNotDraw(false);
mProgressBar = new SwipeProgressBar(this);
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mProgressBarHeight = (int) (metrics.density * PROGRESS_BAR_HEIGHT);
mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
mAccelerateInterpolator = new AccelerateInterpolator(ACCELERATE_INTERPOLATION_FACTOR);
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
setEnabled(a.getBoolean(0, true));
a.recycle();
}
#Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
removeCallbacks(mCancel);
removeCallbacks(mReturnToStartPosition);
}
#Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallbacks(mReturnToStartPosition);
removeCallbacks(mCancel);
}
private void animateOffsetToStartPosition(int from, AnimationListener listener) {
mFrom = from;
mAnimateToStartPosition.reset();
mAnimateToStartPosition.setDuration(mMediumAnimationDuration);
mAnimateToStartPosition.setAnimationListener(listener);
mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
mTarget.startAnimation(mAnimateToStartPosition);
}
/**
* Set the listener to be notified when a refresh is triggered via the swipe
* gesture.
*/
public void setOnRefreshListener(SwipeActivity swipeActivity) {
mListener = swipeActivity;
}
private void setTriggerPercentage(float percent) {
if (percent == 0f) {
// No-op. A null trigger means it's uninitialized, and setting it to zero-percent
// means we're trying to reset state, so there's nothing to reset in this case.
mCurrPercentage = 0;
return;
}
mCurrPercentage = percent;
mProgressBar.setTriggerPercentage(percent);
}
/**
* Notify the widget that refresh state has changed. Do not call this when
* refresh is triggered by a swipe gesture.
*
* #param refreshing Whether or not the view should show refresh progress.
*/
public void setRefreshing(boolean refreshing) {
if (mRefreshing != refreshing) {
ensureTarget();
mCurrPercentage = 0;
mRefreshing = refreshing;
if (mRefreshing) {
mProgressBar.start();
} else {
mProgressBar.stop();
}
}
}
/**
* Set the four colors used in the progress animation. The first color will
* also be the color of the bar that grows in response to a user swipe
* gesture.
*
* #param colorRes1 Color resource.
* #param colorRes2 Color resource.
* #param colorRes3 Color resource.
* #param colorRes4 Color resource.
*/
public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
ensureTarget();
final Resources res = getResources();
final int color1 = res.getColor(colorRes1);
final int color2 = res.getColor(colorRes2);
final int color3 = res.getColor(colorRes3);
final int color4 = res.getColor(colorRes4);
mProgressBar.setColorScheme(color1, color2, color3,color4);
}
/**
* #return Whether the SwipeRefreshWidget is actively showing refresh
* progress.
*/
public boolean isRefreshing() {
return mRefreshing;
}
private void ensureTarget() {
// Don't bother getting the parent height if the parent hasn't been laid out yet.
if (mTarget == null) {
if (getChildCount() > 1 && !isInEditMode()) {
throw new IllegalStateException(
"SwipeRefreshLayout can host only one direct child");
}
mTarget = getChildAt(0);
mOriginalOffsetTop = mTarget.getTop() + getPaddingTop();
}
if (mDistanceToTriggerSync == -1) {
if (getParent() != null && ((View)getParent()).getHeight() > 0) {
final DisplayMetrics metrics = getResources().getDisplayMetrics();
mDistanceToTriggerSync = (int) Math.min(
((View) getParent()) .getHeight() * MAX_SWIPE_DISTANCE_FACTOR,
REFRESH_TRIGGER_DISTANCE * metrics.density);
}
}
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
mProgressBar.draw(canvas);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int width = getMeasuredWidth();
final int height = getMeasuredHeight();
mProgressBar.setBounds(0, 0, width, mProgressBarHeight);
if (getChildCount() == 0) {
return;
}
final View child = getChildAt(0);
final int childLeft = getPaddingLeft();
final int childTop = mCurrentTargetOffsetTop + getPaddingTop();
final int childWidth = width - getPaddingLeft() - getPaddingRight();
final int childHeight = height - getPaddingTop() - getPaddingBottom();
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 1 && !isInEditMode()) {
throw new IllegalStateException("SwipeRefreshLayout can host only one direct child");
}
if (getChildCount() > 0) {
getChildAt(0).measure(
MeasureSpec.makeMeasureSpec(
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(
getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
MeasureSpec.EXACTLY));
}
}
/**
* #return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
public boolean canChildScrollUp() {
if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget();
boolean handled = false;
if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
}
if (isEnabled() && !mReturningToStart && !canChildScrollUp()) {
handled = onTouchEvent(ev);
}
return !handled ? super.onInterceptTouchEvent(ev) : handled;
}
#Override
public void requestDisallowInterceptTouchEvent(boolean b) {
// Nope.
}
#Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
boolean handled = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
mCurrPercentage = 0;
mDownEvent = MotionEvent.obtain(event);
mPrevY = mDownEvent.getY();
break;
case MotionEvent.ACTION_MOVE:
if (mDownEvent != null && !mReturningToStart) {
final float eventY = event.getY();
float yDiff = eventY - mDownEvent.getY();
if (yDiff > mTouchSlop) {
// User velocity passed min velocity; trigger a refresh
if (yDiff > mDistanceToTriggerSync) {
// User movement passed distance; trigger a refresh
startRefresh();
handled = true;
break;
} else {
// Just track the user's movement
setTriggerPercentage(
mAccelerateInterpolator.getInterpolation(
yDiff / mDistanceToTriggerSync));
float offsetTop = yDiff;
if (mPrevY > eventY) {
offsetTop = yDiff - mTouchSlop;
}
updateContentOffsetTop((int) (offsetTop));
if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) {
// If the user puts the view back at the top, we
// don't need to. This shouldn't be considered
// cancelling the gesture as the user can restart from the top.
removeCallbacks(mCancel);
} else {
updatePositionTimeout();
}
mPrevY = event.getY();
handled = true;
}
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mDownEvent != null) {
mDownEvent.recycle();
mDownEvent = null;
}
break;
}
return handled;
}
private void startRefresh() {
removeCallbacks(mCancel);
mReturnToStartPosition.run();
setRefreshing(true);
mListener.onRefresh();
}
private void updateContentOffsetTop(int targetTop) {
final int currentTop = mTarget.getTop();
if (targetTop > mDistanceToTriggerSync) {
targetTop = (int) mDistanceToTriggerSync;
} else if (targetTop < 0) {
targetTop = 0;
}
setTargetOffsetTopAndBottom(targetTop - currentTop);
}
private void setTargetOffsetTopAndBottom(int offset) {
mTarget.offsetTopAndBottom(offset);
mCurrentTargetOffsetTop = mTarget.getTop();
}
private void updatePositionTimeout() {
removeCallbacks(mCancel);
postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT);
}
/**
* Classes that wish to be notified when the swipe gesture correctly
* triggers a refresh should implement this interface.
*/
public interface OnRefreshListener {
public void onRefresh();
}
/**
* Simple AnimationListener to avoid having to implement unneeded methods in
* AnimationListeners.
*/
private class BaseAnimationListener implements AnimationListener {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
}
}
I have some dependent scrollview means (when I scroll one view others will also scroll). I am able to do it properly, but now I want to manage the speed of scroll means (when I scroll my scrollview1 than scrollview2 should scroll +10 and scrollview3 should scroll +20 or whatever speed) same for other (scrollview2, and scrollview3) also.
I check there is a method called scrollview.scrollto(x,y). Which used to manage the scroll but when i increase scollto(x, y+scrollviews(i).getSpeed()) than it gives me stackOverflow exception.
I am attaching my code please look into this and give me some suggestion how can solve this problem.
My custom scrollView class is:
public class CustomVerticalObserveScroll extends ScrollView {
private GestureDetector mGestureDetector;
View.OnTouchListener mGestureListener;
public CustomVerticalObserveScroll(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, new YScrollDetector());
setFadingEdgeLength(0);
// TODO Auto-generated constructor stub
}
private CustomScrollLisner scrollViewListener = null;
public CustomVerticalObserveScroll(Context context) {
super(context);
mGestureDetector = new GestureDetector(context, new YScrollDetector());
setFadingEdgeLength(0);
}
public CustomVerticalObserveScroll(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
mGestureDetector = new GestureDetector(context, new YScrollDetector());
setFadingEdgeLength(0);
}
public void setScrollViewListener(CustomScrollLisner scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
#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 SimpleOnGestureListener {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
if (Math.abs(distanceY) > Math.abs(distanceX)) {
return true;
}
if (Math.abs(distanceX) > Math.abs(distanceY)) {
return true;
}
return false;
}
}
}
and this code I am using to make scroll dependent scroll views.
public class RelativePanoFeature implements IFeaturetype, OnTouchListener {
private String type;
private FeatureCordinates locationCordinates;
private int mOrientation;
private String image;
private FeatureCordinates triggerCordinates;
private String scrollDirection;
private String scrollSpeed;
private String scrollHandler;
CustomVerticalObserveScroll vertical_scroll;
CustomHorizontalObserveScroll horizontal_scroll;
RelativeLayout vsChild;
long timeonDown, timeonUp;
Thread t;
Handler mhandler;
float downx, downy;
int touchId;
public static int scrollid = 0;
public static ArrayList<RelativePanoHandler> storePanoHandler = new ArrayList<RelativePanoHandler>();
public RelativePanoFeature(String type) {
this.type = type;
}
#Override
public void setType(String type) {
this.type = type;
}
#Override
public String getType() {
return type;
}
public void setImage(String image) {
this.image = image;
}
public String getImage() {
return image;
}
public FeatureCordinates getLocation() {
return locationCordinates;
}
public void setLocation(FeatureCordinates featureCordinates) {
this.locationCordinates = featureCordinates;
}
public void setOrientation(int mOrientation) {
this.mOrientation = mOrientation;
}
public int getOrientation() {
return mOrientation;
}
public FeatureCordinates getTrigger() {
return triggerCordinates;
}
public void setTrigger(FeatureCordinates trigger) {
this.triggerCordinates = trigger;
}
public void setScrollDirection(String scrollDirection) {
this.scrollDirection = scrollDirection;
}
public String getScrollDirection() {
return scrollDirection;
}
public void setScrollSpeed(String scrollSpeed) {
this.scrollSpeed = scrollSpeed;
}
public String getScrollSpeed() {
return scrollSpeed;
}
public void setScrollHandler(String scrollHandler) {
this.scrollHandler = scrollHandler;
}
public String getScrollHandler() {
return scrollHandler;
}
public void setTouchId(int touchid) {
this.touchId = touchid;
}
public int getTOuchId() {
return touchId;
}
/* function to draw relative pano */
public void drawRelativePano(final Context con,
final RelativeLayout parent, final Handler handle) {
/* splitting the path from images key in the string */
RelativePanoHandler panHandler = new RelativePanoHandler();
final int height;
final int width;
vsChild = new RelativeLayout(con);
mhandler = handle;
/* giving size of of vertical scroll's child */
LayoutParams imageViewLayoutParams = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
vsChild.setLayoutParams(imageViewLayoutParams);
/* splitting the path from images key in the string */
String path[] = getImage().split("images");
try {
/* Initialise loader to load the image inside child */
BackgroundImageLoader loader = new BackgroundImageLoader(vsChild,
Property.FILEPATH + path[1], con);
try {
loader.execute();
} catch (IllegalStateException e) {
e.printStackTrace();
}
/* getting height and width of image from loader object */
height = loader.get().getHeight();
width = loader.get().getWidth();
/*
* condition for putting the child view in vertical scroll and
* implementing the multi directional scroll for event pano
*/
int locWidth = getLocation().getWidth(), locHeight = getLocation()
.getHeight();
System.out.println("Width= " + width + " Location width= "
+ locWidth);
System.out.println("Heoght= " + height + " Location Height= "
+ locHeight
);
if (width > (getLocation().getWidth())
|| height > (getLocation().getHeight())) {
vertical_scroll = new CustomVerticalObserveScroll(con);
horizontal_scroll = new CustomHorizontalObserveScroll(con);
vertical_scroll.setFillViewport(true);
horizontal_scroll.setFillViewport(true);
vertical_scroll.setId(scrollid);
horizontal_scroll.setFadingEdgeLength(0);
/*
* adding the soft later on vertical and horizontal scroll if
* the detected device is on api level 10 or more than that
*/
if (Build.VERSION.SDK_INT > 10) {
vertical_scroll
.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
horizontal_scroll.setLayerType(View.LAYER_TYPE_SOFTWARE,
null);
}
vsChild.setEnabled(true);
/*
* parameters for setting the height and width of vertical
* scroll
*/
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
getLocation().getWidth(), getLocation().getHeight());
params.leftMargin = getLocation().getX();
params.topMargin = getLocation().getY();
vertical_scroll.setLayoutParams(params);
/* adding vertical scroll child this child will hold the image */
vertical_scroll.addView(vsChild, width, height);
horizontal_scroll.setLayoutParams(params);
/*
* adding vertical scroll as a child of horizontal scroll for
* multidirectional scrolling
*/
horizontal_scroll.addView(vertical_scroll);
/*
* at last add this horizontal scroll in side parent which will
* hold the multidirectional scroll
*/
parent.setTag(getScrollHandler());
parent.addView(horizontal_scroll);
// vertical_scroll.setId(id)
panHandler.setVerticalScroll(vertical_scroll);
panHandler.setHandlerTag(getScrollHandler());
panHandler.setPanoSpeed(Integer.parseInt(getScrollSpeed()));
storePanoHandler.add(panHandler);
int size = storePanoHandler.size();
System.out.println("Vertical Scroll objec size=" + size);
}
System.out.println("TAg= " + parent.getTag());
String scdir = getScrollDirection();
System.out.println("Scroll Directoion= " + scdir);
scrollid++;
if (getScrollDirection().equalsIgnoreCase("Y")) {
vertical_scroll.setScrollViewListener(new CustomScrollLisner() {
#Override
public void onScrollChanged(
CustomHorizontalObserveScroll scrollView, int x,
int y, int oldx, int oldy) {
}
#Override
public void onScrollChanged(
CustomVerticalObserveScroll scrollView, int x,
int y, int oldx, int oldy) {
if (scrollView == storePanoHandler.get(getTOuchId())
.getVerticalScroll()) {
for (int i = 0; i < storePanoHandler.size(); i++) {
storePanoHandler.get(i).getVerticalScroll()
.scrollTo(x, y);
storePanoHandler.get(i).getVerticalScroll().
// storePanoHandler.
// .get(i)
// .getVerticalScroll()
// .scrollTo(
// x,
// oldy
// + storePanoHandler.get(
// i)
// .getPanoSpeed());
}
}
}
});
}
// if (getScrollDirection().equalsIgnoreCase("X")) {
// vertical_scroll.setScrollViewListener(new CustomScrollLisner() {
//
// #Override
// public void onScrollChanged(
// CustomHorizontalObserveScroll scrollView, int x,
// int y, int oldx, int oldy) {
// // if (scrollView == storePanoHandler.get(getTOuchId())
// // .getVerticalScroll()) {
// //
// // for (int i = 0; i < storePanoHandler.size(); i++) {
// // storePanoHandler.get(i).getVerticalScroll()
// // .smoothScrollTo(x, y);
// //
// // }
// // }
// }
//
// #Override
// public void onScrollChanged(
// CustomVerticalObserveScroll scrollView, int x,
// int y, int oldx, int oldy) {
// }
// });
// }
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
/*
* set touch listeners on vertical and horizontal scrolls it will use to
* disable the scroll for it's parent like [image or view pager when
* user interacting with any of custom scroll]
*/
horizontal_scroll.setOnTouchListener(this);
vertical_scroll.setOnTouchListener(this);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
boolean scrollingPanaroma = true;
// changing token value for getting scroll
if (v == vertical_scroll || v == horizontal_scroll) {
/*
* Disabling the parent control [list, pager] when user interacting
* with multidirectional scroll
*/
System.out.println("Pano touch Id= " + vertical_scroll.getId());
setTouchId(vertical_scroll.getId());
if (scrollingPanaroma == true) {
v.getParent().getParent().getParent()
.requestDisallowInterceptTouchEvent(true);
}
/*
* enable the parent control [list, pager] when user done with
* multidirectional scroll
*/
else {
v.getParent().getParent().getParent()
.requestDisallowInterceptTouchEvent(false);
}
}
return false;
}
}
Please help me to solve this out because I am really stucked at this point. Thnaks.
Here is the code I used to slow down the scroll speed of ScrollView programmatically,
ObjectAnimator anim = ObjectAnimator.ofInt(mScrollView, "scrollY", mScrollView.getBottom());
anim.setDuration(9000);
anim.start();
mScrollView - Your ScrollView
mScrollView = (ScrollView) findViewById(R.id.scrollView1);
anima.setDuration(int Value) - greater the value, slower the scroll
I used the code block in Switch Button OnCheckedChangedListener.
I need to load only new items on pull to refresh, remaining loaded items should move down after the new items are loaded. I am loading 10,10 items in load more once again if I do pull to refresh I want all loaded items on top with newly updated item.
How to do that?
((PullAndLoadListView)getListView()).setOnRefreshListener(new OnRefreshListener(){public void onRefresh(){}}
I would definitely take a look at Chris Banes' implementation of pull-to-refresh. His code does not only include this interaction style for ListView, but also GridView and WebView.
Especially the latter will be of interest in your case, since it's an example implementation of pull-to-refresh for a view that does not use an adapter for its content. If you look at the source code, you'll see that every concrete pull-to-refreh view in Banes' project extends from a generic PullToRefreshBase, which contains most of the logic for animation and refreshing. The benefit of having that base class is that doing the same thing for any other type of view, e.g. a TextView, should be pretty straightforward.
The only drawback of this approach is that the implementation is a wrapper for other views, meaning you'll have write a couple of extra lines to get the actual view. So it's not a full drop-in replacement. However, its functionality and features far exceed that little inconvenience.
EDITED :
Look at the Below Code i have implemented
in your Main JAVA File :
PullToRefreshListView listview;
listview = (PullToRefreshListView) findViewById(R.id.airpost_listView);
listview.setOnRefreshListener(new OnRefreshListener() {
public void onRefresh() {
// TODO Auto-generated method stub
new GetPost().execute(); // AsyncTask where you can put your logic when to call the listview for getting new data or let it be same as before
}
});
Listview Defined in xml File:
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/bhavesh"
android:padding="5dp" >
<com.FlightMate.AirpostTab.PullToRefreshListView
android:id="#+id/airpost_listView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:cacheColorHint="#android:color/transparent"
android:divider="#00000000" />
</LinearLayout>
Here is PullToRefreshListView :
package com.FlightMate.AirpostTab;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;
public class PullToRefreshListView extends PullToRefreshBase<ListView> {
public PullToRefreshListView(Context context) {
super(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected final ListView createAdapterView(Context context,
AttributeSet attrs) {
ListView lv = new ListView(context, attrs);
// Set it to this so it can be used in ListActivity/ListFragment
lv.setId(android.R.id.list);
return lv;
}
}
here is PullToRefreshBase :
package com.FlightMate.AirpostTab;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.LinearLayout;
import com.FlightMate.R;
public abstract class PullToRefreshBase<T extends AbsListView> extends
LinearLayout implements OnTouchListener, OnScrollListener {
private final class SmoothScrollRunnable implements Runnable {
static final int ANIMATION_DURATION_MS = 190;
static final int ANIMATION_FPS = 1000 / 60;
private final Interpolator interpolator;
private final int scrollToY;
private final int scrollFromY;
private final Handler handler;
private boolean continueRunning = true;
private long startTime = -1;
private int currentY = -1;
public SmoothScrollRunnable(Handler handler, int fromY, int toY) {
this.handler = handler;
this.scrollFromY = fromY;
this.scrollToY = toY;
this.interpolator = new AccelerateDecelerateInterpolator();
}
// #Override
public void run() {
/**
* Only set startTime if this is the first time we're starting, else
* actually calculate the Y delta
*/
if (startTime == -1) {
startTime = System.currentTimeMillis();
} else {
/**
* We do do all calculations in long to reduce software float
* calculations. We use 1000 as it gives us good accuracy and
* small rounding errors
*/
long normalizedTime = (1000 * (System.currentTimeMillis() - startTime))
/ ANIMATION_DURATION_MS;
normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0);
final int deltaY = Math
.round((scrollFromY - scrollToY)
* interpolator
.getInterpolation(normalizedTime / 1000f));
this.currentY = scrollFromY - deltaY;
setHeaderScroll(currentY);
}
// If we're not at the target Y, keep going...
if (continueRunning && scrollToY != currentY) {
handler.postDelayed(this, ANIMATION_FPS);
}
}
public void stop() {
this.continueRunning = false;
this.handler.removeCallbacks(this);
}
};
// ===========================================================
// Constants
// ===========================================================
static final int PULL_TO_REFRESH = 0;
static final int RELEASE_TO_REFRESH = PULL_TO_REFRESH + 1;
static final int REFRESHING = RELEASE_TO_REFRESH + 1;
static final int EVENT_COUNT = 3;
public static final int MODE_PULL_DOWN_TO_REFRESH = 0x1;
public static final int MODE_PULL_UP_TO_REFRESH = 0x2;
public static final int MODE_BOTH = 0x3;
// ===========================================================
// Fields
// ===========================================================
private int state = PULL_TO_REFRESH;
private int mode = MODE_PULL_DOWN_TO_REFRESH;
private int currentMode;
private boolean disableScrollingWhileRefreshing = true;
private T adapterView;
private boolean isPullToRefreshEnabled = true;
private LoadingLayout headerLayout;
private LoadingLayout footerLayout;
private int headerHeight;
private final Handler handler = new Handler();
private OnTouchListener onTouchListener;
private OnRefreshListener onRefreshListener;
private OnScrollListener onScrollListener;
private OnLastItemVisibleListener onLastItemVisibleListener;
private int lastSavedFirstVisibleItem = -1;
private SmoothScrollRunnable currentSmoothScrollRunnable;
private float startY = -1;
private final float[] lastYs = new float[EVENT_COUNT];
// ===========================================================
// Constructors
// ===========================================================
public PullToRefreshBase(Context context) {
this(context, null);
}
public PullToRefreshBase(Context context, int mode) {
this(context);
this.mode = mode;
}
public PullToRefreshBase(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
// ===========================================================
// Getter & Setter
// ===========================================================
/**
* Get the Wrapped AdapterView. Anything returned here has already been
* added to the content view.
*
* #return The AdapterView which is currently wrapped
*/
public final T getAdapterView() {
return adapterView;
}
/**
* Whether Pull-to-Refresh is enabled
*
* #return enabled
*/
public final boolean isPullToRefreshEnabled() {
return isPullToRefreshEnabled;
}
public void setDisableScrollingWhileRefreshing(
boolean disableScrollingWhileRefreshing) {
this.disableScrollingWhileRefreshing = disableScrollingWhileRefreshing;
}
/**
* Mark the current Refresh as complete. Will Reset the UI and hide the
* Refreshing View
*/
public final void onRefreshComplete() {
resetHeader();
}
public final void setOnLastItemVisibleListener(
OnLastItemVisibleListener listener) {
onLastItemVisibleListener = listener;
}
public final void setOnRefreshListener(OnRefreshListener listener) {
onRefreshListener = listener;
}
/**
* A mutator to enable/disable Pull-to-Refresh for the current AdapterView
*
* #param enable
* Whether Pull-To-Refresh should be used
*/
public final void setPullToRefreshEnabled(boolean enabled) {
this.isPullToRefreshEnabled = enabled;
}
public final void setReleaseLabel(String releaseLabel) {
if (null != headerLayout) {
headerLayout.setReleaseLabel(releaseLabel);
}
if (null != footerLayout) {
footerLayout.setReleaseLabel(releaseLabel);
}
}
public final void setPullLabel(String pullLabel) {
if (null != headerLayout) {
headerLayout.setPullLabel(pullLabel);
}
if (null != footerLayout) {
footerLayout.setPullLabel(pullLabel);
}
}
public final void setRefreshingLabel(String refreshingLabel) {
if (null != headerLayout) {
headerLayout.setRefreshingLabel(refreshingLabel);
}
if (null != footerLayout) {
footerLayout.setRefreshingLabel(refreshingLabel);
}
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
public final void setOnScrollListener(OnScrollListener listener) {
onScrollListener = listener;
}
#Override
public final void setOnTouchListener(OnTouchListener listener) {
onTouchListener = listener;
}
public final void onScroll(final AbsListView view,
final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
if (null != onLastItemVisibleListener) {
// detect if last item is visible
if (visibleItemCount > 0 && visibleItemCount < totalItemCount
&& (firstVisibleItem + visibleItemCount == totalItemCount)) {
// only process first event
if (firstVisibleItem != lastSavedFirstVisibleItem) {
lastSavedFirstVisibleItem = firstVisibleItem;
onLastItemVisibleListener.onLastItemVisible();
}
}
}
if (null != onScrollListener) {
onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount,
totalItemCount);
}
}
public final void onScrollStateChanged(final AbsListView view,
final int scrollState) {
if (null != onScrollListener) {
onScrollListener.onScrollStateChanged(view, scrollState);
}
}
// #Override
public final boolean onTouch(View view, MotionEvent ev) {
if (isPullToRefreshEnabled) {
// Returning true here stops the ListView being scrollable while we
// refresh
if (state == REFRESHING && disableScrollingWhileRefreshing) {
return true;
} else if (onAdapterViewTouch(view, ev)) {
return true;
}
}
if (null != onTouchListener) {
return onTouchListener.onTouch(view, ev);
}
return false;
}
/**
* This is implemented by derived classes to return the created AdapterView.
* If you need to use a custom AdapterView (such as a custom ListView),
* override this method and return an instance of your custom class.
*
* Be sure to set the ID of the view in this method, especially if you're
* using a ListActivity or ListFragment.
*
* #param context
* #param attrs
* AttributeSet from wrapped class. Means that anything you
* include in the XML layout declaration will be routed to the
* AdapterView
* #return New instance of the AdapterView
*/
protected abstract T createAdapterView(Context context, AttributeSet attrs);
// ===========================================================
// Methods
// ===========================================================
protected final void resetHeader() {
state = PULL_TO_REFRESH;
initializeYsHistory();
startY = -1;
if (null != headerLayout) {
headerLayout.reset();
}
if (null != footerLayout) {
footerLayout.reset();
}
smoothScrollTo(0);
}
private void init(Context context, AttributeSet attrs) {
setOrientation(LinearLayout.VERTICAL);
// Styleables from XML
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PullToRefresh);
mode = a.getInteger(R.styleable.PullToRefresh_mode,
MODE_PULL_DOWN_TO_REFRESH);
// AdapterView
// By passing the attrs, we can add ListView/GridView params via XML
adapterView = this.createAdapterView(context, attrs);
adapterView.setOnTouchListener(this);
adapterView.setOnScrollListener(this);
addView(adapterView, new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, 0, 1.0f));
// Loading View Strings
String pullLabel = context
.getString(R.string.pull_to_refresh_pull_label);
String refreshingLabel = context
.getString(R.string.pull_to_refresh_refreshing_label);
String releaseLabel = context
.getString(R.string.pull_to_refresh_release_label);
// Add Loading Views
if (mode == MODE_PULL_DOWN_TO_REFRESH || mode == MODE_BOTH) {
headerLayout = new LoadingLayout(context,
MODE_PULL_DOWN_TO_REFRESH, releaseLabel, pullLabel,
refreshingLabel);
addView(headerLayout, 0, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
measureView(headerLayout);
headerHeight = headerLayout.getMeasuredHeight();
}
if (mode == MODE_PULL_UP_TO_REFRESH || mode == MODE_BOTH) {
footerLayout = new LoadingLayout(context, MODE_PULL_UP_TO_REFRESH,
releaseLabel, pullLabel, refreshingLabel);
addView(footerLayout, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
measureView(footerLayout);
headerHeight = footerLayout.getMeasuredHeight();
}
// Styleables from XML
if (a.hasValue(R.styleable.PullToRefresh_headerTextColor)) {
final int color = a.getColor(
R.styleable.PullToRefresh_headerTextColor, Color.BLACK);
if (null != headerLayout) {
headerLayout.setTextColor(color);
}
if (null != footerLayout) {
footerLayout.setTextColor(color);
}
}
if (a.hasValue(R.styleable.PullToRefresh_headerBackground)) {
this.setBackgroundResource(a.getResourceId(
R.styleable.PullToRefresh_headerBackground, Color.WHITE));
}
if (a.hasValue(R.styleable.PullToRefresh_adapterViewBackground)) {
adapterView.setBackgroundResource(a.getResourceId(
R.styleable.PullToRefresh_adapterViewBackground,
Color.WHITE));
}
a.recycle();
// Hide Loading Views
switch (mode) {
case MODE_BOTH:
setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(),
-headerHeight);
break;
case MODE_PULL_UP_TO_REFRESH:
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
-headerHeight);
break;
case MODE_PULL_DOWN_TO_REFRESH:
default:
setPadding(getPaddingLeft(), -headerHeight, getPaddingRight(),
getPaddingBottom());
break;
}
// If we're not using MODE_BOTH, then just set currentMode to current
// mode
if (mode != MODE_BOTH) {
currentMode = mode;
}
}
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
private boolean onAdapterViewTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
updateEventStates(event);
if (isPullingToRefresh() && startY == -1) {
startY = event.getY();
// Need to set current Mode if we're using both
if (mode == MODE_BOTH) {
if (isUserDraggingDownwards()) {
currentMode = MODE_PULL_DOWN_TO_REFRESH;
} else if (isUserDraggingUpwards()) {
currentMode = MODE_PULL_UP_TO_REFRESH;
}
}
return false;
}
if (startY != -1 && !adapterView.isPressed()) {
pullEvent(event, startY);
return true;
}
break;
case MotionEvent.ACTION_UP:
initializeYsHistory();
startY = -1;
if (state == RELEASE_TO_REFRESH) {
setRefreshing();
if (onRefreshListener != null) {
onRefreshListener.onRefresh();
}
} else {
smoothScrollTo(0);
}
break;
}
return false;
}
private void pullEvent(MotionEvent event, float firstY) {
float averageY = average(lastYs);
final int height;
switch (currentMode) {
case MODE_PULL_UP_TO_REFRESH:
height = (int) Math.max(firstY - averageY, 0);
setHeaderScroll(height);
break;
case MODE_PULL_DOWN_TO_REFRESH:
default:
height = (int) Math.max(averageY - firstY, 0);
setHeaderScroll(-height);
break;
}
if (state == PULL_TO_REFRESH && headerHeight < height) {
state = RELEASE_TO_REFRESH;
if (null != headerLayout) {
headerLayout.releaseToRefresh();
}
if (null != footerLayout) {
footerLayout.releaseToRefresh();
}
} else if (state == RELEASE_TO_REFRESH && headerHeight >= height) {
state = PULL_TO_REFRESH;
if (null != headerLayout) {
headerLayout.pullToRefresh();
}
if (null != footerLayout) {
footerLayout.pullToRefresh();
}
}
}
private void setHeaderScroll(int y) {
scrollTo(0, y);
}
private void setRefreshing() {
state = REFRESHING;
if (null != headerLayout) {
headerLayout.refreshing();
}
if (null != footerLayout) {
footerLayout.refreshing();
}
switch (currentMode) {
case MODE_PULL_DOWN_TO_REFRESH:
smoothScrollTo(-headerHeight);
break;
case MODE_PULL_UP_TO_REFRESH:
smoothScrollTo(headerHeight);
break;
}
}
private float average(float[] ysArray) {
float avg = 0;
for (int i = 0; i < EVENT_COUNT; i++) {
avg += ysArray[i];
}
return avg / EVENT_COUNT;
}
private void initializeYsHistory() {
for (int i = 0; i < EVENT_COUNT; i++) {
lastYs[i] = 0;
}
}
private void updateEventStates(MotionEvent event) {
for (int i = 0, z = event.getHistorySize(); i < z; i++) {
this.updateEventStates(event.getHistoricalY(i));
}
this.updateEventStates(event.getY());
}
private void updateEventStates(float y) {
for (int i = 0; i < EVENT_COUNT - 1; i++) {
lastYs[i] = lastYs[i + 1];
}
lastYs[EVENT_COUNT - 1] = y;
}
private boolean isPullingToRefresh() {
if (isPullToRefreshEnabled && state != REFRESHING) {
switch (mode) {
case MODE_PULL_DOWN_TO_REFRESH:
return isFirstItemVisible() && isUserDraggingDownwards();
case MODE_PULL_UP_TO_REFRESH:
return isLastItemVisible() && isUserDraggingUpwards();
case MODE_BOTH:
return (isFirstItemVisible() && isUserDraggingDownwards())
|| (isLastItemVisible() && isUserDraggingUpwards());
}
}
return false;
}
private boolean isFirstItemVisible() {
if (this.adapterView.getCount() == 0) {
return true;
} else if (adapterView.getFirstVisiblePosition() == 0) {
return adapterView.getChildAt(0).getTop() >= adapterView.getTop();
} else {
return false;
}
}
private boolean isLastItemVisible() {
final int count = this.adapterView.getCount();
if (count == 0) {
return true;
} else if (adapterView.getLastVisiblePosition() == count - 1) {
return true;
} else {
return false;
}
}
private boolean isUserDraggingDownwards() {
return this.isUserDraggingDownwards(0, EVENT_COUNT - 1);
}
private boolean isUserDraggingDownwards(int from, int to) {
return lastYs[from] != 0 && lastYs[to] != 0
&& Math.abs(lastYs[from] - lastYs[to]) > 10
&& lastYs[from] < lastYs[to];
}
private boolean isUserDraggingUpwards() {
return this.isUserDraggingUpwards(0, EVENT_COUNT - 1);
}
private boolean isUserDraggingUpwards(int from, int to) {
return lastYs[from] != 0 && lastYs[to] != 0
&& Math.abs(lastYs[to] - lastYs[from]) > 10
&& lastYs[to] < lastYs[from];
}
private void smoothScrollTo(int y) {
if (null != currentSmoothScrollRunnable) {
currentSmoothScrollRunnable.stop();
}
this.currentSmoothScrollRunnable = new SmoothScrollRunnable(handler,
getScrollY(), y);
handler.post(currentSmoothScrollRunnable);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
public static interface OnRefreshListener {
public void onRefresh();
}
public static interface OnLastItemVisibleListener {
public void onLastItemVisible();
}
}
Hope it will Help you out from your Problem. let me know if you are still finding any difficulties.