diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..a4763d1 --- /dev/null +++ b/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..99c12c9 --- /dev/null +++ b/.project @@ -0,0 +1,33 @@ + + + XListView + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..4457c1d --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 0000000..b1cdf17 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..5a70945 --- /dev/null +++ b/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-7 diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..8074c4c Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-hdpi/xlistview_arrow.png b/res/drawable-hdpi/xlistview_arrow.png new file mode 100644 index 0000000..511fa31 Binary files /dev/null and b/res/drawable-hdpi/xlistview_arrow.png differ diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..1095584 Binary files /dev/null and b/res/drawable-ldpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..a07c69f Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100644 index 0000000..bc12cd8 --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/xlistview_footer.xml b/res/layout/xlistview_footer.xml new file mode 100644 index 0000000..dc15ef5 --- /dev/null +++ b/res/layout/xlistview_footer.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/xlistview_header.xml b/res/layout/xlistview_header.xml new file mode 100755 index 0000000..2ca3034 --- /dev/null +++ b/res/layout/xlistview_header.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..c6e417c --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,13 @@ + + + + Hello World, XListViewActivity! + XListView + 下拉刷新 + 松开刷新数据 + 正在加载... + 上次更新时间: + 查看更多 + 松开载入更多 + + \ No newline at end of file diff --git a/src/me/maxwin/XListViewActivity.java b/src/me/maxwin/XListViewActivity.java new file mode 100644 index 0000000..bdca6d6 --- /dev/null +++ b/src/me/maxwin/XListViewActivity.java @@ -0,0 +1,13 @@ +package me.maxwin; + +import android.app.Activity; +import android.os.Bundle; + +public class XListViewActivity extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } +} \ No newline at end of file diff --git a/src/me/maxwin/view/XListView.java b/src/me/maxwin/view/XListView.java new file mode 100755 index 0000000..d51840c --- /dev/null +++ b/src/me/maxwin/view/XListView.java @@ -0,0 +1,353 @@ +/** + * @file WBListView.java + * @package com.tencent.weibu.view + * @create Mar 18, 2012 6:28:41 PM + * @author maxiezhang@tencent.com + * @description 添加了滚动触边时的回滚效果,暂时仅支持垂直滚动。 + */ +package me.maxwin.view; + +import me.maxwin.R; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.animation.DecelerateInterpolator; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.Scroller; + +public class XListView extends ListView implements OnScrollListener { + + private final static String TAG = "XListView"; + + private float mLastY = -1; // save event y + private Scroller mScroller; // used for scroll back + private OnScrollListener mScrollListener; // user's scroll listener + + // -- pull down to refresh + private View mHeaderView; + private View mHeaderContentView; + + // current header height + private int mHeaderHeight; + private boolean mEnablePullRefresh = true; + private boolean mPullRefreshing = false; // is refreshing? + // refresh/load listener + private IXListViewListener mListViewListener; + + // -- pull up to load + private View mFooterView; + private View mFooterContentView; + private boolean mEnablePullLoad = false; + private boolean mPullLoading = false; + + // total items, use to test whether the footer view is showing. + private int mTotalItemCount; + + // mScroller will reset header or footer, use this status to identify. + private int mScrollBackStatus; + private final static int SCROLLBACK_HEADER = 0; + private final static int SCROLLBACK_FOOTER = 1; + + // duration for scroll back. + private final static int SCROLL_DURATION = 400; // ms + private final static int PULL_LOAD_MORE_DELTA = 50; // trigger for load more + private final static float OFFSET_RADIO = 1.8f; // iOS like + + /** + * @param context + */ + public XListView(Context context) { + super(context); + initWithContext(context); + } + + public XListView(Context context, AttributeSet attrs) { + super(context, attrs); + initWithContext(context); + } + + public XListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initWithContext(context); + } + + private void initWithContext(Context context) { + mScroller = new Scroller(context, new DecelerateInterpolator()); + // XListView need the scroll info. It will dispatch the message to + // user's listener as well. + super.setOnScrollListener(this); + + // init header view. + mHeaderView = LayoutInflater.from(context).inflate( + R.layout.xlistview_header, null); + mHeaderContentView = mHeaderView.findViewById(R.id.xlistview_header_content); + addHeaderView(mHeaderView); + + // init footer view. + mFooterView = LayoutInflater.from(context).inflate( + R.layout.xlistview_footer, null); + + // init header view's height + mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( + new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + mHeaderHeight = mHeaderContentView.getHeight(); + getViewTreeObserver() + .removeGlobalOnLayoutListener(this); + } + }); + } + + @Override + public void setAdapter(ListAdapter adapter) { + // add load more view just before user call setAdapter. + addFooterView(mFooterView); + super.setAdapter(adapter); + } + + /** + * enable/disable pull refresh. + * @param enable + */ + public void setPullRefreshEnable(boolean enable) { + mEnablePullRefresh = enable; + if (!mEnablePullRefresh) { // disable + mHeaderContentView.setVisibility(View.INVISIBLE); + } else { + mHeaderContentView.setVisibility(View.VISIBLE); + } + } + + /** + * 设置启用上拉载入更多 + * + * @param enable + */ + public void setPullLoadEnable(boolean enable) { + mEnablePullLoad = enable; + if (!mEnablePullLoad) { + mPullLoadViewContent.setVisibility(View.INVISIBLE); + mPullLoadView.setOnClickListener(null); + } else { + mPullLoading = false; + mPullLoadViewContent.setVisibility(View.VISIBLE); + mPullLoadView.setState(LoadMoreView.STATE_NORMAL); + mPullLoadView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + startLoadMore(); + } + }); + } + } + + /** + * 停止下拉刷新,回推下拉控件 + */ + public void stopRefresh() { + if (mPullRefreshing == true) { + mPullRefreshing = false; + resetHeaderHeight(); + } + } + + /** + * 停止上拉载入更多 + */ + public void stopLoadMore() { + if (mPullLoading == true) { + mPullLoading = false; + mPullLoadView.setState(LoadMoreView.STATE_NORMAL); + } + } + + /** + * 触发滚动回调,统一入口,会检查mScrollListener合法性 + */ + private void invokeOnScrolling() { + if (mScrollListener instanceof OnWBScrollListener) { + OnWBScrollListener l = (OnWBScrollListener) mScrollListener; + l.onWBScrolling(this); + } + } + + private void updateHeaderHeight(float delta) { + mPullRefreshView.setVisiableHeight((int) delta + + mPullRefreshView.getVisiableHeight()); + if (mEnablePullRefresh && !mPullRefreshing) { // 未处于刷新状态,更新箭头 + if (mPullRefreshView.getVisiableHeight() > mPullRefreshViewHeight) { + mPullRefreshView.setState(PullRefreshView.STATE_READY); + } else { + mPullRefreshView.setState(PullRefreshView.STATE_NORMAL); + } + } + setSelection(0); + } + + /** + * 重置刷新视图高度 + */ + private void resetHeaderHeight() { + int height = mPullRefreshView.getVisiableHeight(); + if (height == 0) + return; + // 正在刷新,且刷新视图仅显示部分,不做处理 + if (mPullRefreshing && height <= mPullRefreshViewHeight) { + return; + } + int finalHeight = 0; + // 正在刷新,回退到仅显示刷新视图 + if (mPullRefreshing && height > mPullRefreshViewHeight) { + finalHeight = mPullRefreshViewHeight; + } + mScrollBack = SCROLLBACK_HEADER; + mScroller.startScroll(0, height, 0, finalHeight - height, + SCROLL_DURATION); + + invalidate(); + } + + private void updateFooterHeight(float delta) { + int height = mPullLoadView.getBottomMargin() + (int) delta; + if (mEnablePullLoad && !mPullLoading) { + if (height > PULL_LOAD_MORE_DELTA) { + mPullLoadView.setState(LoadMoreView.STATE_READY); + } else { + mPullLoadView.setState(LoadMoreView.STATE_NORMAL); + } + } + mPullLoadView.setBottomMargin(height); + + setSelection(mTotalItemCount - 1); + } + + private void resetFooterHeight() { + int bottomMargin = mPullLoadView.getBottomMargin(); + if (bottomMargin > 0) { + mScrollBack = SCROLLBACK_FOOTER; + mScroller.startScroll(0, bottomMargin, 0, -bottomMargin, + SCROLL_DURATION); + invalidate(); + } + } + + private void startLoadMore() { + mPullLoading = true; + mPullLoadView.setState(LoadMoreView.STATE_LOADING); + if (mPullRefreshCallbacker != null) { + mPullRefreshCallbacker.onLoadMore(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mLastY == -1) { + mLastY = ev.getRawY(); + } + + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mLastY = ev.getRawY(); + break; + case MotionEvent.ACTION_MOVE: + final float deltaY = ev.getRawY() - mLastY; + mLastY = ev.getRawY(); + if (getFirstVisiblePosition() == 0 + && (mPullRefreshView.getVisiableHeight() > 0 || deltaY > 0)) { + // 第一行,且下拉组件已显示或者正在下拉 + updateHeaderHeight(deltaY / OFFSET_RADIO); + invokeOnScrolling(); + } else if (getLastVisiblePosition() == mTotalItemCount - 1 + && (mPullLoadView.getBottomMargin() > 0 || deltaY < 0)) { + // 最后一行,上来组件已显示或者正在上来 + updateFooterHeight(-deltaY / OFFSET_RADIO); + } + break; + default: + mLastY = -1; + if (getFirstVisiblePosition() == 0) { + // 触发刷新 + if (mEnablePullRefresh + && mPullRefreshView.getVisiableHeight() > mPullRefreshViewHeight) { + mPullRefreshing = true; + mPullRefreshView.setState(PullRefreshView.STATE_REFRESHING); + if (mPullRefreshCallbacker != null) { + mPullRefreshCallbacker.onRefresh(); + } + } + resetHeaderHeight(); + } else if (getLastVisiblePosition() == mTotalItemCount - 1) { + // 触发载入更多 + if (mEnablePullLoad + && mPullLoadView.getBottomMargin() > PULL_LOAD_MORE_DELTA) { + startLoadMore(); + } + resetFooterHeight(); + } + break; + } + return super.onTouchEvent(ev); + } + + @Override + public void computeScroll() { + if (mScroller.computeScrollOffset()) { + // Qzlog.d(TAG, "computeScroll:" + mScroller.getCurrY()); + if (mScrollBack == SCROLLBACK_HEADER) { + mPullRefreshView.setVisiableHeight(mScroller.getCurrY()); + } else { + mPullLoadView.setBottomMargin(mScroller.getCurrY()); + } + postInvalidate(); + invokeOnScrolling(); + } + super.computeScroll(); + } + + @Override + public void setOnScrollListener(OnScrollListener l) { + mScrollListener = l; + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (mScrollListener != null) { + mScrollListener.onScrollStateChanged(view, scrollState); + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + // send to user's listener + mTotalItemCount = totalItemCount; + if (mScrollListener != null) { + mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, + totalItemCount); + } + } + + public void setPullRefreshListener(IXListViewListener l) { + mPullRefreshCallbacker = l; + } + + // 滚动接口 + public interface OnWBScrollListener extends OnScrollListener { + public void onWBScrolling(View view); + } + + // 下拉刷新接口 + public interface IXListViewListener { + public void onRefresh(); + + public void onLoadMore(); + } +}