From cfac119d15c3359eba720ac323f579150874df5d Mon Sep 17 00:00:00 2001 From: Maxwin-z Date: Thu, 17 May 2012 08:23:15 +0800 Subject: [PATCH] first commit --- .classpath | 8 + .project | 33 +++ AndroidManifest.xml | 23 ++ proguard.cfg | 40 +++ project.properties | 11 + res/drawable-hdpi/ic_launcher.png | Bin 0 -> 4147 bytes res/drawable-hdpi/xlistview_arrow.png | Bin 0 -> 1456 bytes res/drawable-ldpi/ic_launcher.png | Bin 0 -> 1723 bytes res/drawable-mdpi/ic_launcher.png | Bin 0 -> 2574 bytes res/layout/main.xml | 12 + res/layout/xlistview_footer.xml | 27 ++ res/layout/xlistview_header.xml | 65 +++++ res/values/strings.xml | 13 + src/me/maxwin/XListViewActivity.java | 13 + src/me/maxwin/view/XListView.java | 353 ++++++++++++++++++++++++++ 15 files changed, 598 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 AndroidManifest.xml create mode 100644 proguard.cfg create mode 100644 project.properties create mode 100644 res/drawable-hdpi/ic_launcher.png create mode 100644 res/drawable-hdpi/xlistview_arrow.png create mode 100644 res/drawable-ldpi/ic_launcher.png create mode 100644 res/drawable-mdpi/ic_launcher.png create mode 100644 res/layout/main.xml create mode 100644 res/layout/xlistview_footer.xml create mode 100755 res/layout/xlistview_header.xml create mode 100644 res/values/strings.xml create mode 100644 src/me/maxwin/XListViewActivity.java create mode 100755 src/me/maxwin/view/XListView.java 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 0000000000000000000000000000000000000000..8074c4c571b8cd19e27f4ee5545df367420686d7 GIT binary patch literal 4147 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 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/xlistview_arrow.png b/res/drawable-hdpi/xlistview_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..511fa3197b538b8500a22d22280e6f33085d9491 GIT binary patch literal 1456 zcmbVMZA=?w9KTYA701?rOom-J513t`y}RDEM=x~r9Z-up?Kwg$~KRB-?-kCCzf$4C?y<3fCdj9H*nT~c1B zwqXcdg-ERyXe}terxtVwA_E%rh%Shj5YU9`Q4A*tq7*bBC;}q}7&Ying2W6Yf`ID} zBzqI-CerJ4ultfM3)CV>VG@R;(WpL(=>@SFM$Kk(O2c5#$p~GfgO{k7j*k>>C^(r& zP-MdrEAU`SkqQXyk_D0@T}y!rdpw)Md}O^)a>?Kr6^2ng0&`p{uT^wJ@-qL1u_Zd< z>j*Qjmx&1NVo*Mhros)fT)Rg*N&)3)$QMOcJ`^hC6oTy>!%Njp3nc&1(=1IoP_w~I z7zhXMz+4E5+Dy3H?KELV2kv&cQQL;bR$RhtwxKxTaAAZ2MO}yuL+y5ZnGG`$Wdvs2 zPPB)f(8GM62}a3h8xuGOr|OgLI*BoLRwZg!dx99rkn|EFTG91NUl zjsLXD#+KZFspV$l%f{yDFuXi3qC6NE&K-`)vpHYwwE1GQfkrFvih3+*%>-I0=Z}pC z6>95*CY!TO&z~$B+mZdvsiMUkFs|zVymy>Cmk@VPq@$qe+1r_aF2;5wn^h-vS%Eo> z!s=}9sr0+^eWnIv{yF65h9A6F&kjD_cRKx3#cHqU z@kGA9RvBnF++P8TkKeF)Nd<9MJGDKnpiis)X&~+Q5`W&p zzH7*~`N+hzUkkwHw4^t=`y!Gd-hZ&C_phO1TsfNi1q&|sRQ|yS?vB-H`tR5t*7)qR z$8H~0WVN5Khb}FBr(}-ybY_(RD#Y#0|Amf!h2MGpvk|N2r8=ZP!<{qN0%_U-?XKB( zz8ycGv6OzqTYkJ@0FtghAk_nvU!MTS{fc4Wlx`VFj>nha%OWsyWyIYYu2aTK%QKS& z30?2Z(B6(C@xy~nxViWl?UNyG)%yeSB~|Io{rgXD8=SU>u0MP%>)y_p51(kX=JXy^ zT^dz-=6oN$@wTD_zp+R?e(%B*pP|(4jW4L%w0{??SN4sofj2+)R#Z-C&ZO?pYFCZ( Ivc2)xKNs!q_W%F@ literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1095584ec21f71cd0afc9e0993aa2209671b590c GIT binary patch literal 1723 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 + + + + + \ 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(); + } +}