Java tutorial
/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gome.haoyuangong.views; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import com.gome.haoyuangong.R; import com.gome.haoyuangong.utils.DateUtils; import android.content.Context; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.support.v4.view.ViewCompat; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.DecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.view.animation.Transformation; import android.widget.AbsListView; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; /** * * * @author winter * */ public class SwipeRefreshLayout extends ViewGroup { private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f; private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f; private static final int REFRESH_TRIGGER_DISTANCE = 120; private final static int RELEASE_To_REFRESH = 0;// ?? private final static int PULL_To_REFRESH = 1;// ? private final static int REFRESHING = 2;// ? private final static int DONE = 3;// ?? private final static int REFRESHED = 4;// ?? private View mTarget; // the header private int mOriginalOffsetTop; private OnRefreshListener mListener; private MotionEvent mDownEvent; private int mFrom; private boolean mRefreshing = false; private int mTouchSlop; private float mDistanceToTriggerSync = -1; private int mMediumAnimationDuration; private int mProgressBarHeight; private int mCurrentTargetOffsetTop; private int mDownTargetTop; // 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 boolean isDisAllowIntercept = false; private int mHeaderHeight; private int state;// ?? private LinearLayout headView;// private RotateAnimation animation;// private RotateAnimation reverseAnimation;// private TextView tipsTextview;// ?? private TextView lastUpdatedTextView;// private ImageView arrowImageView;// private ProgressBar progressBar;// ? private String tag; private SharedPreferences sharedPreferences; 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 - mOriginalOffsetTop - mHeaderHeight; setTargetOffsetTopAndBottom(offset); } }; private final Animation mAnimateToHeaderPosition = new Animation() { @Override public void applyTransformation(float interpolatedTime, Transformation t) { int targetTop = mHeaderHeight; int headerOriginal = mHeaderHeight + getPaddingTop(); if (mFrom != headerOriginal) { targetTop = (mFrom + (int) ((headerOriginal - mFrom) * interpolatedTime)); } int offset = targetTop - headerOriginal; setTargetOffsetTopAndBottom(offset); } }; 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 mReturningToStart = false; } }; // Cancel the refresh gesture and animate everything back to its original // state. private final Runnable mReturnToStartPosition = new Runnable() { @Override public void run() { mReturningToStart = true; animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(), mReturnToStartPositionListener); } }; // animate move to header showing (refreshing) private final Runnable mMoveHeader = new Runnable() { @Override public void run() { mReturningToStart = true; animateOffsetToHeaderPosition(mCurrentTargetOffsetTop + getPaddingTop(), mReturnToStartPositionListener); } }; /** * Simple constructor to use when creating a SwipeRefreshLayout from code. * * @param context */ public SwipeRefreshLayout(Context context) { this(context, null); sharedPreferences = getContext().getSharedPreferences("freshtime", Context.MODE_PRIVATE); } /** * 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); 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(); animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(250); animation.setFillAfter(true); reverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(200); reverseAnimation.setFillAfter(true); state = DONE; headView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.list_head, null); arrowImageView = (ImageView) headView.findViewById(R.id.head_arrowImageView); // arrowImageView.setMinimumWidth(70); // arrowImageView.setMinimumHeight(50); progressBar = (ProgressBar) headView.findViewById(R.id.head_progressBar); tipsTextview = (TextView) headView.findViewById(R.id.head_tipsTextView); lastUpdatedTextView = (TextView) headView.findViewById(R.id.head_lastUpdatedTextView); measureView(headView); mHeaderHeight = headView.getMeasuredHeight(); headView.setPadding(0, -mHeaderHeight, 0, 0); addView(headView); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); removeCallbacks(mReturnToStartPosition); removeCallbacks(mMoveHeader); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); removeCallbacks(mReturnToStartPosition); removeCallbacks(mMoveHeader); } public void setTag(String tag) { this.tag = tag; getFreshTime(); } public void setFreshTime(Date dt) { String dateString = DateUtils.format(dt, "yyyy-MM-dd HH:mm:ss"); if (sharedPreferences != null && !TextUtils.isEmpty(tag)) sharedPreferences.edit().putString(tag, dateString).commit(); lastUpdatedTextView.setText("" + dateString); } private void getFreshTime() { if (sharedPreferences == null || TextUtils.isEmpty(tag)) return; String dateString = sharedPreferences.getString(tag, ""); sharedPreferences.edit().putString(tag, dateString).commit(); lastUpdatedTextView.setText("" + dateString); } private void animateOffsetToStartPosition(int from, AnimationListener listener) { mFrom = from; mAnimateToStartPosition.reset(); mAnimateToStartPosition.setDuration(mMediumAnimationDuration); mAnimateToStartPosition.setAnimationListener(listener); mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); mTarget.startAnimation(mAnimateToStartPosition); } private void animateOffsetToHeaderPosition(int from, AnimationListener listener) { mFrom = from; mAnimateToHeaderPosition.reset(); mAnimateToHeaderPosition.setDuration(mMediumAnimationDuration); mAnimateToHeaderPosition.setAnimationListener(listener); mAnimateToHeaderPosition.setInterpolator(mDecelerateInterpolator); mTarget.startAnimation(mAnimateToHeaderPosition); } /** * Set the listener to be notified when a refresh is triggered via the swipe * gesture. */ public void setOnRefreshListener(OnRefreshListener listener) { mListener = listener; } /** * 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(); mRefreshing = refreshing; } } /** * @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() > 2 && !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); mDistanceToTriggerSync = REFRESH_TRIGGER_DISTANCE * metrics.density; } } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int width = getMeasuredWidth(); final int height = getMeasuredHeight(); if (getChildCount() == 0) { return; } final View child0 = getChildAt(0); final View child = getChildAt(1); final int childLeft = getPaddingLeft(); final int childTop = mCurrentTargetOffsetTop + getPaddingTop(); final int childWidth = width - getPaddingLeft() - getPaddingRight(); final int childHeight = height - getPaddingTop() - getPaddingBottom(); child0.layout(childLeft, getPaddingTop(), childLeft + child0.getMeasuredWidth(), childTop + mHeaderHeight); child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (getChildCount() > 2 && !isInEditMode()) { throw new IllegalStateException("SwipeRefreshLayout can host only one direct child"); } if (getChildCount() > 1) { getChildAt(1).measure( MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); getChildAt(0).measure(widthMeasureSpec, heightMeasureSpec); } } /** * @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 canChildScroll() { 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); } } private boolean canChildScroll(ViewGroup group, int direction, int startindex) { boolean canScroll = false; if (mCurrentTargetOffsetTop != 0) { return false; } int count = group.getChildCount(); for (int i = startindex; i < count; i++) { View child = group.getChildAt(i); if (child instanceof LinearLayout || child instanceof RelativeLayout) { canScroll = canChildScroll((ViewGroup) child, direction, 0); } else { canScroll = canScroll(child, direction); return canScroll; } } return canScroll; } /** * ??listviewscrollview ??View??touch * move??layout?????? * * @MethodDescription canScroll * @return * @exception * @since 1.0.0 */ private boolean canScroll(View v, int direction) { View childView = v; if (childView instanceof ListView) { if (((ListView) childView).getChildCount() == 0) { return false; } int top = ((ListView) childView).getChildAt(0).getTop(); int pad = ((ListView) childView).getListPaddingTop(); if (direction > 0) { if ((Math.abs(top - pad)) < 3 && ((ListView) childView).getFirstVisiblePosition() == 0) { return false; } else { return true; } } else if (direction < 0) { return true; } } else if (childView instanceof ScrollView) { if (direction > 0) { if (((ScrollView) childView).getScrollY() == 0) { return false; } else { return true; } } else if (direction < 0) { if (((ScrollView) childView).getScrollY() + ((ScrollView) childView).getHeight() >= ((ScrollView) childView).getChildAt(0) .getMeasuredHeight()) { return false; } else { return true; } } } return false; } private int mInterceptY; private int mOldHandled = 0;// 0 ? 1 2? @Override public boolean dispatchTouchEvent(MotionEvent ev) { ensureTarget(); boolean handled = false; if (ev.getAction() == MotionEvent.ACTION_DOWN) { mInterceptY = (int) ev.getY(); } if (isEnabled() && !canChildScroll(this, (int) ev.getY() - mInterceptY, 1)) { if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) { mReturningToStart = false; removeAnimateMove(); } handled = onTouchEvent(ev); } else { mDownEvent = MotionEvent.obtain(ev); mDownTargetTop = mCurrentTargetOffsetTop; } mInterceptY = (int) ev.getY(); if (ev.getAction() == MotionEvent.ACTION_MOVE) { if (mOldHandled == 1 && !handled) { MotionEvent event = MotionEvent.obtain(ev); event.setAction(MotionEvent.ACTION_DOWN); getChildAt(1).dispatchTouchEvent(event); disableSubControls(getChildAt(1)); event.recycle(); } else if (mOldHandled == 2 && handled) { MotionEvent event = MotionEvent.obtain(ev); event.setAction(MotionEvent.ACTION_CANCEL); getChildAt(1).dispatchTouchEvent(event); event.recycle(); } else if (mOldHandled == 0 && handled) { MotionEvent event = MotionEvent.obtain(ev); event.setAction(MotionEvent.ACTION_CANCEL); getChildAt(1).dispatchTouchEvent(event); event.recycle(); } if (handled) { mOldHandled = 1; } else { mOldHandled = 2; } } else if (ev.getAction() == MotionEvent.ACTION_UP) { mOldHandled = 0; } if (!handled) { super.dispatchTouchEvent(ev); } return true; } private void disableSubControls(View view) { if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) { View v = viewGroup.getChildAt(i); if (v instanceof ViewGroup) { if (v instanceof Spinner) { v.setPressed(false); } else if (v instanceof ListView) { removeListTapCallback((ListView) v); } else { removeViewTapCallback(v); disableSubControls((ViewGroup) v); } } else { removeViewTapCallback(v); } } } else { removeViewTapCallback(view); } } private void removeListTapCallback(ListView list) { try { Field field = AbsListView.class.getField("mPendingCheckForTap"); field.setAccessible(true); Runnable c = (Runnable) (field.get(list)); if (c != null) { list.removeCallbacks(c); } } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void removeViewTapCallback(View v) { try { Method m = View.class.getDeclaredMethod("removeTapCallback"); m.setAccessible(true); m.invoke(v); System.out.println(m); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public boolean shouldDelayChildPressedState() { // TODO Auto-generated method stub return true; } @Override public void requestDisallowInterceptTouchEvent(boolean b) { // Nope. isDisAllowIntercept = b; // System.out.println("requestDisallowInterceptTouchEvent:"+b); } private boolean isTouching = false; private boolean isScrolling = false; @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); boolean handled = false; switch (action) { case MotionEvent.ACTION_DOWN: mDownEvent = MotionEvent.obtain(event); mDownTargetTop = mCurrentTargetOffsetTop; if (state == REFRESHED && mDownTargetTop == 0) { setState(DONE); } isTouching = true; isScrolling = false; break; case MotionEvent.ACTION_MOVE: if (mDownEvent != null && !mReturningToStart) { final float eventY = event.getY(); float yDiff1 = (eventY - mDownEvent.getY()); if (Math.abs(yDiff1) > mTouchSlop) { isScrolling = true; } if (state == DONE && Math.abs(yDiff1) > mTouchSlop) { setState(PULL_To_REFRESH); } float yDiff = (yDiff1 / 2); if (state == PULL_To_REFRESH || state == RELEASE_To_REFRESH) { updateContentOffsetTop((int) (yDiff) + mDownTargetTop); if (mCurrentTargetOffsetTop >= mHeaderHeight + 5) { setState(RELEASE_To_REFRESH); } else if (mCurrentTargetOffsetTop <= 0) { setState(DONE); } else { setState(PULL_To_REFRESH); } // if (yDiff < 0) // yDiff = 0; handled = true; } if ((state == REFRESHING || state == REFRESHED) && isScrolling) { updateContentOffsetTop((int) (yDiff) + mDownTargetTop); handled = true; } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: isTouching = false; isScrolling = false; removeCallbacks(mReturnToStartPosition); removeCallbacks(mMoveHeader); if (state == PULL_To_REFRESH || state == REFRESHED) { post(mReturnToStartPosition); } if (state == RELEASE_To_REFRESH) { post(mMoveHeader); setState(REFRESHING); } if (state == REFRESHING) { if (mCurrentTargetOffsetTop > mHeaderHeight) { post(mMoveHeader); } } mDownTargetTop = -1; if (mDownEvent != null) { mDownEvent.recycle(); mDownEvent = null; } break; } return handled; } private void removeAnimateMove() { removeCallbacks(mReturnToStartPosition); removeCallbacks(mMoveHeader); mTarget.clearAnimation(); } private void setState(int state) { if (this.state == state) return; changeHeaderViewByState(this.state, state); this.state = state; } public void changeHeaderViewByState(int oldState, int newState) { switch (newState) { case RELEASE_To_REFRESH: arrowImageView.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); tipsTextview.setVisibility(View.VISIBLE); // lastUpdatedTextView.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.startAnimation(animation); tipsTextview.setText("??"); break; case PULL_To_REFRESH: progressBar.setVisibility(View.GONE); tipsTextview.setVisibility(View.VISIBLE); // lastUpdatedTextView.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.VISIBLE); // RELEASE_To_REFRESH??? if (oldState == RELEASE_To_REFRESH) { arrowImageView.clearAnimation(); arrowImageView.startAnimation(reverseAnimation); } tipsTextview.setText(""); break; case REFRESHING: // headView.setPadding(0, 0, 0, 0); progressBar.setVisibility(View.VISIBLE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.GONE); tipsTextview.setText("..."); // lastUpdatedTextView.setVisibility(View.VISIBLE); startRefresh(); break; case DONE: // headView.setPadding(0, -1 * mHeaderHeight, 0, 0); progressBar.setVisibility(View.GONE); arrowImageView.clearAnimation(); arrowImageView.setImageResource(R.drawable.goicon); tipsTextview.setText(""); // lastUpdatedTextView.setVisibility(View.VISIBLE); break; case REFRESHED: progressBar.setVisibility(View.GONE); arrowImageView.clearAnimation(); arrowImageView.setVisibility(View.GONE); tipsTextview.setText("?"); setFreshTime(new Date()); // lastUpdatedTextView.setVisibility(View.VISIBLE); break; } } private void startRefresh() { setRefreshing(true); if (mListener != null) { mListener.onRefresh(); } } public void startRefreshAuto() { setState(REFRESHING); setTargetOffsetTopAndBottom(0); } public void stopRefresh() { if (state != REFRESHING) { return; } setState(REFRESHED); if (!isTouching) { post(mReturnToStartPosition); } } private void updateContentOffsetTop(int targetTop) { // final int currentTop = mTarget.getPaddingTop(); if (targetTop > mDistanceToTriggerSync) { // targetTop = (int) mDistanceToTriggerSync; } else if (targetTop < 0) { targetTop = 0; } setTargetOffsetTopAndBottom(targetTop - mHeaderHeight); } private void setTargetOffsetTopAndBottom(int offset) { mTarget.setPadding(0, offset, 0, 0); mCurrentTargetOffsetTop = mTarget.getPaddingTop() + mHeaderHeight; } /** * 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) { } } private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_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); } }