Java tutorial
/* * Copyright 2016 The Open Source Project of Jackie Zhu * * 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. * * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ * $ $ * $ _oo0oo_ $ * $ o8888888o $ * $ 88" . "88 $ * $ (| -_- |) $ * $ 0\ = /0 $ * $ ___/`---'\___ $ * $ .' \\| |$ '. $ * $ / \\||| : |||$ \ $ * $ / _||||| -:- |||||- \ $ * $ | | \\\ - $/ | | $ * $ | \_| ''\---/'' |_/ | $ * $ \ .-\__ '-' ___/-. / $ * $ ___'. .' /--.--\ `. .'___ $ * $ ."" '< `.___\_<|>_/___.' >' "". $ * $ | | : `- \`.;`\ _ /`;.`/ - ` : | | $ * $ \ \ `_. \_ __\ /__ _/ .-` / / $ * $ =====`-.____`.___ \_____/___.-`___.-'===== $ * $ `=---=' $ * $ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ $ * $ $ * $ Buddha bless Never BUG $ * $ $ * $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ */ package com.jackie.refresh; import android.content.Context; import android.graphics.Color; import android.support.v4.view.MotionEventCompat; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.RotateAnimation; import android.widget.*; import com.jackie.refresh.listener.OnLoadListener; import com.jackie.refresh.listener.OnRefreshListener; import java.text.SimpleDateFormat; import java.util.Date; /** * Created by on 16/5/11. * * @param <T> * @author Jackie Zhu * @version 1.0 */ public abstract class RefreshLayoutBase<T extends View> extends ViewGroup implements AbsListView.OnScrollListener { private static final String TAG = "RefreshLayoutBase"; /** * ,?,,ListView, GridView */ protected T mContentView; /** * */ protected Scroller mScroller; /** * Header View */ protected View mHeaderView; /** * ? */ protected int mScreenHeight; /** * Header View */ protected ImageView mArrowImg; /** * Header View ? */ protected ProgressBar mRefreshProgress; /** * Header View */ protected TextView mTipsTxt; /** * Header View */ protected TextView mTimeTxt; /** * Header View */ protected int mHeaderHeight; /** * */ protected View mFooterView; /** * ??,Header View? */ protected int mInitScrollY; /** * ?Y?? */ protected int mLastY; /** * Y????? */ protected int mYOffset; /** * ?? */ private boolean isArrowUp; /** * ? */ public static final int STATUS_IDLE = 0; /** * ?? */ public static final int STATUS_PULL_TO_REFRESH = 1; /** * ? */ public static final int STATUS_RELEASE_TO_REFRESH = 2; /** * */ public static final int STATUS_REFRESHING = 3; /** * */ public static final int STATUS_LOADING = 4; /** * ?? */ protected int mCurrentStatus = STATUS_IDLE; /** * ? */ protected OnRefreshListener mOnRefreshListener; /** * ? */ protected OnLoadListener mOnLoadListener; public RefreshLayoutBase(Context context) { this(context, null); } /** * @param context * @param attrs */ public RefreshLayoutBase(Context context, AttributeSet attrs) { super(context, attrs); // ? mScroller = new Scroller(context); // ?? mScreenHeight = context.getResources().getDisplayMetrics().heightPixels; // header?1/4 mHeaderHeight = mScreenHeight / 4; // ? initLayout(context); } /** * ? * * @param context */ private void initLayout(Context context) { // header view setupHeaderView(context); // setupContentView(context); // ? setDefaultContentLayoutParam(); addView(mContentView); // Footer View setupFooterView(context); } /** * ? Header View * * @param context */ private void setupHeaderView(Context context) { Log.d(TAG, "setupHeaderView: "); mHeaderView = LayoutInflater.from(context).inflate(R.layout.header_refresh, this, false); mHeaderView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mHeaderHeight)); mHeaderView.setBackgroundColor(Color.RED); // Header View?1/4,,?100px, paddingTop, // mHeaderView.setPadding(0, mHeaderHeight - 100, 0, 0); addView(mHeaderView); // ?Header View? mArrowImg = (ImageView) mHeaderView.findViewById(R.id.img_arrow_image); mRefreshProgress = (ProgressBar) mHeaderView.findViewById(R.id.progress_refresh); mTipsTxt = (TextView) mHeaderView.findViewById(R.id.txt_pull_to_refresh); mTimeTxt = (TextView) mHeaderView.findViewById(R.id.txt_update_at); } /** * ?,?? * * @param context */ protected abstract void setupContentView(Context context); /** * ? */ protected void setDefaultContentLayoutParam() { ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); mContentView.setLayoutParams(params); } /** * ? * * @param context */ private void setupFooterView(Context context) { mFooterView = LayoutInflater.from(context).inflate(R.layout.footer_refresh, this, false); addView(mFooterView); } public T getContentView() { return mContentView; } public View getHeaderView() { return mHeaderView; } public View getFooterView() { return mFooterView; } public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.mOnRefreshListener = onRefreshListener; } public void setOnLoadListener(OnLoadListener onLoadListener) { this.mOnLoadListener = onLoadListener; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // MeasureSpec int width = MeasureSpec.getSize(widthMeasureSpec); // ? int finalHeight = 0; for (int i = 0; i < getChildCount(); i++) { View childView = getChildAt(i); // ?? measureChild(childView, widthMeasureSpec, heightMeasureSpec); // ???, finalHeight += childView.getMeasuredHeight(); } // setMeasuredDimension(width, finalHeight); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int left = getPaddingLeft(); int top = getPaddingTop(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(left, top, child.getMeasuredWidth(), child.getMeasuredHeight() + top); top += child.getMeasuredHeight(); } // ?y? mInitScrollY = mHeaderView.getMeasuredHeight() + getPaddingTop(); // Header View?,???header View scrollTo(0, mInitScrollY); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // ?,,?,? if (mOnLoadListener != null && isBottom() && mScroller.getCurrY() <= mInitScrollY && mYOffset < 0 && mCurrentStatus == STATUS_IDLE) { // Footer View showFooterView(); doLoadMore(); mYOffset = 0; } } @Override public void computeScroll() { // ??,computeScrollOffset()false? if (mScroller.computeScrollOffset()) { // View?(x, y)?? scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); // postInvalidate(); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "onInterceptTouchEvent() called with: " + "ev = [" + ev.toString() + "]"); // ? final int action = MotionEventCompat.getActionMasked(ev); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { return false; } switch (action) { case MotionEvent.ACTION_DOWN: mLastY = (int) ev.getRawY(); break; case MotionEvent.ACTION_MOVE: mYOffset = (int) ev.getRawX() - mLastY; // ,,,onTouchEvent()?? if (isTop() && mYOffset > 0) { return true; } break; } return false; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "onTouchEvent() called with: " + "event = [" + event.toString() + "]"); switch (event.getAction()) { case MotionEvent.ACTION_MOVE: // // ??y?? int currentY = (int) event.getRawY(); // ??? mYOffset = currentY - mLastY; if (mCurrentStatus != STATUS_LOADING) { // Y? changeScrollY(mYOffset); } // Header View rotateHeaderArrow(); // Header View? changeTips(); // mLastYY?? mLastY = currentY; break; case MotionEvent.ACTION_UP: if (isTop()) { // ? doRefresh(); } break; default: break; } // return super.onTouchEvent(event); return true; } /** * y,Header View * * @param distance ???(???) */ private void changeScrollY(int distance) { int currentY = getScrollY(); if (distance > 0 && currentY - distance > getPaddingTop()) { scrollBy(0, -distance); } else if (distance < 0 && currentY - distance <= mInitScrollY) { // scrollBy(0, -distance); } currentY = getScrollY(); int slop = mInitScrollY / 2; if (currentY > 0 && currentY < slop) { mCurrentStatus = STATUS_RELEASE_TO_REFRESH; } else if (currentY > slop && currentY < mInitScrollY) { mCurrentStatus = STATUS_PULL_TO_REFRESH; } else { mCurrentStatus = STATUS_IDLE; } } private void rotateHeaderArrow() { Log.d(TAG, "rotateHeaderArrow() called with: mCurrentStatus = " + mCurrentStatus); if (mCurrentStatus == STATUS_REFRESHING) { return; } else if (mCurrentStatus == STATUS_PULL_TO_REFRESH && !isArrowUp) { return; } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH && isArrowUp) { return; } mRefreshProgress.setVisibility(View.GONE); mArrowImg.setVisibility(View.VISIBLE); float pivotX = mArrowImg.getWidth() / 2f; float pivotY = mArrowImg.getHeight() / 2f; float fromDegrees = 0f; float toDegrees = 0f; if (mCurrentStatus == STATUS_PULL_TO_REFRESH) { fromDegrees = 180f; toDegrees = 360f; } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) { fromDegrees = 0f; toDegrees = 180f; } RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY); animation.setDuration(100); animation.setFillAfter(true); mArrowImg.startAnimation(animation); if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) { isArrowUp = true; } else { isArrowUp = false; } } private void changeTips() { if (mCurrentStatus == STATUS_PULL_TO_REFRESH) { mTipsTxt.setText(R.string.txt_pull_to_refresh); } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) { mTipsTxt.setText(R.string.up_to_refresh); } } /** * ? */ private void doRefresh() { changeHeaderViewStatus(); // ? if (mCurrentStatus == STATUS_REFRESHING && mOnRefreshListener != null) { // mOnRefreshListener.onRefresh(); } } /** * ,????,?Header View?(1/2), * ?,????? */ private void changeHeaderViewStatus() { Log.d(TAG, "changeHeaderViewStatus: "); int curScrollY = getScrollY(); // 1/2,? if (curScrollY < mInitScrollY / 2) { mRefreshProgress.setVisibility(VISIBLE); mArrowImg.setVisibility(View.GONE); // Header View? mScroller.startScroll(getScrollX(), curScrollY, 0, mHeaderView.getPaddingTop() - curScrollY); mCurrentStatus = STATUS_REFRESHING; mTipsTxt.setText(R.string.txt_tip_refreshing); } else { mScroller.startScroll(getScrollX(), curScrollY, 0, mInitScrollY - curScrollY); mCurrentStatus = STATUS_IDLE; } invalidate(); } /** * ?,??? */ public void refreshComplete() { mCurrentStatus = STATUS_IDLE; // ??Header View mScroller.startScroll(getScrollX(), getScrollY(), 0, mInitScrollY - getScrollY()); invalidate(); updateHeaderTimeStamp(); // 200??arrowprogressBar,?? this.postDelayed(new Runnable() { @Override public void run() { mRefreshProgress.setVisibility(View.GONE); mArrowImg.setVisibility(View.VISIBLE); } }, 100); } /** * Header View */ private void updateHeaderTimeStamp() { // mTimeTxt.setText(R.string.txt_last_update_time); SimpleDateFormat sdf = (SimpleDateFormat) SimpleDateFormat.getInstance(); sdf.applyPattern("yyyy-MM-dd HH:mm:ss"); mTimeTxt.append(sdf.format(new Date())); } public void loadCompute() { // ??Footer View startScroll(mInitScrollY - getScrollY()); mCurrentStatus = STATUS_IDLE; } /** * Footer View */ private void showFooterView() { startScroll(mFooterView.getMeasuredHeight()); mCurrentStatus = STATUS_LOADING; } /** * ? */ private void doLoadMore() { if (mOnLoadListener != null) { mOnLoadListener.onLoadMore(); } } private void startScroll(int offSetY) { mScroller.startScroll(getScrollX(), getScrollY(), 0, offSetY); invalidate(); } /** * ?,??,{@code true} * * @return <b> * true: <br/> * false: * </b> */ protected abstract boolean isTop(); /** * ?,??,{@code true} * * @return <b> * true: <br/> * false: * </b> */ protected abstract boolean isBottom(); }