我按照SwipeRefreshLayout.class編寫了RecyclerViewRefresh。當我拉視圖直到它不移動然後釋放時,視圖重置爲原始視圖。問題是該視圖應該觸發定時器,然後該定時器重置視圖。我找不到原因。LinearLayout滾動到頂部
請告訴我爲什麼offsetTopAndBottom()可以使視圖自動回到原來的位置。謝謝。
我使用setY()來解決這個問題。但我也想知道爲什麼。而我讀了offsetTopAndBottom()的源碼,也找不到任何線索。
RecyclerViewRefresh代碼:
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();
}}
我已經使用setY()來解決這個問題。但我也想知道爲什麼。而我讀了offsetTopAndBottom()的源碼,也找不到任何線索。 –