main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root" android:layout_width="fill_parent" android:layout_height="fill_parent" > <com.curiousby.demoxlistview.xlistview.MsgListView android:id="@+id/mylist" android:layout_width="fill_parent" android:layout_height="fill_parent" android:transcriptMode="normal" > </com.curiousby.demoxlistview.xlistview.MsgListView> </LinearLayout>
xlistview_header.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/coversation_bg" android:gravity="bottom" > <RelativeLayout android:id="@+id/xlistview_header_content" android:layout_width="fill_parent" android:layout_height="60dp" android:paddingLeft="40dip" > <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" > <ImageView android:id="@+id/xlistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/refresh_arrow_2" /> <ProgressBar android:id="@+id/xlistview_header_progressbar" android:layout_width="16dip" android:layout_height="16dip" android:layout_gravity="center" android:indeterminate="true" android:indeterminateBehavior="repeat" android:indeterminateDrawable="@drawable/common_loading4" android:visibility="invisible" /> </FrameLayout> <LinearLayout android:id="@+id/xlistview_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/xlistview_header_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" android:text="@string/xlistview_header_hint_normal" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" android:text="@string/xlistview_header_last_time" android:textSize="12sp" /> <TextView android:id="@+id/xlistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="刚刚" android:textColor="#ffffff" android:textSize="12sp" /> </LinearLayout> </LinearLayout> </RelativeLayout> </LinearLayout>
xlistview_footer.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#0fff" > <RelativeLayout android:id="@+id/xlistview_footer_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" > <LinearLayout android:id="@+id/xlistview_footer_progressbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginBottom="2dp" android:layout_marginTop="2dp" android:gravity="center" android:orientation="horizontal" > <ProgressBar android:layout_width="16dip" android:layout_height="16dip" android:indeterminate="true" android:indeterminateBehavior="repeat" android:indeterminateDrawable="@drawable/common_loading3" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dip" android:text="@string/xlistview_header_hint_loading" android:textColor="#000000" /> </LinearLayout> <TextView android:id="@+id/xlistview_footer_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/xlistview_footer_hint_normal" android:textColor="#000000" android:visibility="invisible" /> </RelativeLayout> </LinearLayout>
message_header.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#0fff" android:gravity="bottom" > <RelativeLayout android:id="@+id/msg_header_content" android:layout_width="fill_parent" android:layout_height="30dp" > <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" > <ProgressBar android:id="@+id/xlistview_header_progressbar" android:layout_width="16dip" android:layout_height="16dip" android:layout_gravity="center" android:indeterminate="true" android:indeterminateBehavior="repeat" android:indeterminateDrawable="@drawable/common_loading3" android:visibility="invisible" /> <TextView android:id="@+id/xlistview_header_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="显示更多消息" android:textColor="#000000" /> </FrameLayout> </RelativeLayout> </LinearLayout>
package com.curiousby.demoxlistview; import java.util.ArrayList; import java.util.List; import com.curiousby.demoxlistview.xlistview.MsgListView; import com.curiousby.demoxlistview.xlistview.MsgListView.IXListViewListener; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.app.Activity; import android.content.Context; public class MainActivity extends Activity implements IXListViewListener, OnTouchListener { private List<String> mlist = new ArrayList<String>(); private MylistAdapter mAdapter; private Context context; private MsgListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); context = MainActivity.this; mAdapter = new MylistAdapter( this); mAdapter.setMlist(mlist); listView = (MsgListView) findViewById(R.id.mylist); listView.setOnTouchListener(this); listView.setPullLoadEnable(false); listView.setXListViewListener(this); listView.setAdapter(mAdapter); listView.setSelection(mAdapter.getCount() - 1); mlist.add("a"); mAdapter.notifyDataSetChanged(); } private int i; @Override public void onRefresh() { mlist.add(""+i++); mAdapter.notifyDataSetChanged(); listView.setSelection(mAdapter.getCount() - 1); listView.stopRefresh(); } @Override public void onLoadMore() { mlist.add(""+i++); listView.setSelection(mAdapter.getCount() - 1); mAdapter.notifyDataSetChanged(); } @Override public boolean onTouch(View arg0, MotionEvent arg1) { return false; } }
package com.curiousby.demoxlistview; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; public class MylistAdapter extends BaseAdapter{ private List<String> mlist; private LayoutInflater mInflater; private Context context; public MylistAdapter( Context context) { this.mInflater = LayoutInflater.from(context); this.context = context; } public void setMlist(List<String> mlist) { this.mlist = mlist; notifyDataSetChanged(); } @Override public int getCount() { if (mlist != null ) { return mlist.size(); } return 0; } @Override public String getItem(int position) { if (mlist != null ) { return (String) mlist.get(position); } return ""; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { String item = getItem(position); ViewHolder viewHolder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item, parent, false); viewHolder =new ViewHolder(convertView); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag( ); } viewHolder.text.setText(item); return convertView; } class ViewHolder { private TextView text; ViewHolder(View baseView){ this.text = (TextView) baseView.findViewById(R.id.text); } } }
/** * @file XListViewHeader.java * @create Apr 18, 2012 5:22:27 PM * @author Maxwin * @description XListView's header */ package com.curiousby.demoxlistview.xlistview; import com.curiousby.demoxlistview.R; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; public class XListViewHeader extends LinearLayout { private LinearLayout mContainer; private ImageView mArrowImageView; private ProgressBar mProgressBar; private TextView mHintTextView; private int mState = STATE_NORMAL; private Animation mRotateUpAnim; private Animation mRotateDownAnim; private final int ROTATE_ANIM_DURATION = 180; public final static int STATE_NORMAL = 0; public final static int STATE_READY = 1; public final static int STATE_REFRESHING = 2; public XListViewHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public XListViewHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { // 初始情况,设置下拉刷新view高度为0 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate( R.layout.xlistview_header, null); addView(mContainer, lp); setGravity(Gravity.BOTTOM); mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow); mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview); mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar); mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } public void setState(int state) { if (state == mState) return; if (state == STATE_REFRESHING) { // 显示进度 mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.VISIBLE); } else { // 显示箭头图片 mArrowImageView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.INVISIBLE); } switch (state) { case STATE_NORMAL: if (mState == STATE_READY) { mArrowImageView.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mArrowImageView.clearAnimation(); } mHintTextView.setText(R.string.xlistview_header_hint_normal); break; case STATE_READY: if (mState != STATE_READY) { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mHintTextView.setText(R.string.xlistview_header_hint_ready); } break; case STATE_REFRESHING: mHintTextView.setText(R.string.xlistview_header_hint_loading); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getVisiableHeight() { return mContainer.getHeight(); } }
/** * @file XFooterView.java * @create Mar 31, 2012 9:33:43 PM * @author Maxwin * @description XListView's footer */ package com.curiousby.demoxlistview.xlistview; import com.curiousby.demoxlistview.R; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; public class XListViewFooter extends LinearLayout { public final static int STATE_NORMAL = 0; public final static int STATE_READY = 1; public final static int STATE_LOADING = 2; private Context mContext; private View mContentView; private View mProgressBar; private TextView mHintView; public XListViewFooter(Context context) { super(context); initView(context); } public XListViewFooter(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public void setState(int state) { mHintView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.INVISIBLE); mHintView.setVisibility(View.INVISIBLE); if (state == STATE_READY) { mHintView.setVisibility(View.VISIBLE); mHintView.setText(R.string.xlistview_footer_hint_ready); } else if (state == STATE_LOADING) { mProgressBar.setVisibility(View.VISIBLE); } else { mHintView.setVisibility(View.VISIBLE); mHintView.setText(R.string.xlistview_footer_hint_normal); } } public void setBottomMargin(int height) { if (height < 0) return ; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.bottomMargin = height; mContentView.setLayoutParams(lp); } public int getBottomMargin() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); return lp.bottomMargin; } /** * normal status */ public void normal() { mHintView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.GONE); } /** * loading status */ public void loading() { mHintView.setVisibility(View.GONE); mProgressBar.setVisibility(View.VISIBLE); } /** * hide footer when disable pull load more */ public void hide() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.height = 0; mContentView.setLayoutParams(lp); } /** * show footer */ public void show() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.height = LayoutParams.WRAP_CONTENT; mContentView.setLayoutParams(lp); } private void initView(Context context) { mContext = context; LinearLayout moreView = (LinearLayout)LayoutInflater.from(mContext).inflate(R.layout.xlistview_footer, null); addView(moreView); moreView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); mContentView = moreView.findViewById(R.id.xlistview_footer_content); mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar); mHintView = (TextView)moreView.findViewById(R.id.xlistview_footer_hint_textview); } }
/** * @file XListView.java * @package me.maxwin.view * @create Mar 18, 2012 6:28:41 PM * @author Maxwin * @description An ListView support (a) Pull down to refresh, (b) Pull up to load more. * Implement IXListViewListener, and see stopRefresh() / stopLoadMore(). */ package com.curiousby.demoxlistview.xlistview; import com.curiousby.demoxlistview.R; import android.content.Context; import android.util.AttributeSet; 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.ExpandableListView; import android.widget.ListAdapter; import android.widget.RelativeLayout; import android.widget.Scroller; import android.widget.TextView; public class XExpandableListView extends ExpandableListView implements OnScrollListener { private float mLastY = -1; // save event y private Scroller mScroller; // used for scroll back private OnScrollListener mScrollListener; // user's scroll listener // the interface to trigger refresh and load more. private IXListViewListener mListViewListener; // -- header view private XListViewHeader mHeaderView; // header view content, use it to calculate the Header's height. And hide it // when disable pull refresh. private RelativeLayout mHeaderViewContent; private TextView mHeaderTimeView; private int mHeaderViewHeight; // header view's height private boolean mEnablePullRefresh = true;// private boolean mPullRefreshing = false; // is refreashing. // -- footer view private XListViewFooter mFooterView; private boolean mEnablePullLoad; private boolean mPullLoading; private boolean mIsFooterReady = false; // total list items, used to detect is at the bottom of listview. private int mTotalItemCount; // for mScroller, scroll back from header or footer. private int mScrollBack; private final static int SCROLLBACK_HEADER = 0; private final static int SCROLLBACK_FOOTER = 1; private final static int SCROLL_DURATION = 400; // scroll back duration private final static int PULL_LOAD_MORE_DELTA = 50; // when pull up >= 50px // at bottom, trigger // load more. private final static float OFFSET_RADIO = 1.8f; // support iOS like pull // feature. /** * @param context */ public XExpandableListView(Context context) { super(context); initWithContext(context); } public XExpandableListView(Context context, AttributeSet attrs) { super(context, attrs); initWithContext(context); } public XExpandableListView(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 event, and it will dispatch the event to // user's listener (as a proxy). super.setOnScrollListener(this); // init header view mHeaderView = new XListViewHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView .findViewById(R.id.xlistview_header_content); mHeaderTimeView = (TextView) mHeaderView .findViewById(R.id.xlistview_header_time); addHeaderView(mHeaderView); // init footer view mFooterView = new XListViewFooter(context); // init header height mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); } @Override public void setAdapter(ListAdapter adapter) { // make sure XListViewFooter is the last footer view, and only add once. if (mIsFooterReady == false) { mIsFooterReady = true; addFooterView(mFooterView); } super.setAdapter(adapter); } /** * enable or disable pull down refresh feature. * * @param enable */ public void setPullRefreshEnable(boolean enable) { mEnablePullRefresh = enable; if (!mEnablePullRefresh) { // disable, hide the content mHeaderViewContent.setVisibility(View.INVISIBLE); } else { mHeaderViewContent.setVisibility(View.VISIBLE); } } /** * enable or disable pull up load more feature. * * @param enable */ public void setPullLoadEnable(boolean enable) { mEnablePullLoad = enable; if (!mEnablePullLoad) { mFooterView.hide(); mFooterView.setOnClickListener(null); } else { mPullLoading = false; mFooterView.show(); mFooterView.setState(XListViewFooter.STATE_NORMAL); // both "pull up" and "click" will invoke load more. mFooterView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startLoadMore(); } }); } } /** * stop refresh, reset header view. */ public void stopRefresh() { if (mPullRefreshing == true) { mPullRefreshing = false; resetHeaderHeight(); } } /** * stop load more, reset footer view. */ public void stopLoadMore() { if (mPullLoading == true) { mPullLoading = false; mFooterView.setState(XListViewFooter.STATE_NORMAL); } } /** * set last refresh time * * @param time */ public void setRefreshTime(String time) { mHeaderTimeView.setText(time); } public void setRefreshTime(long time) { mHeaderTimeView.setText(TimeUtil.getChatTime(time)); } private void invokeOnScrolling() { if (mScrollListener instanceof OnXScrollListener) { OnXScrollListener l = (OnXScrollListener) mScrollListener; l.onXScrolling(this); } } private void updateHeaderHeight(float delta) { mHeaderView.setVisiableHeight((int) delta + mHeaderView.getVisiableHeight()); if (mEnablePullRefresh && !mPullRefreshing) { // 未处于刷新状态,更新箭头 if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mHeaderView.setState(XListViewHeader.STATE_READY); } else { mHeaderView.setState(XListViewHeader.STATE_NORMAL); } } setSelection(0); // scroll to top each time } /** * reset header view's height. */ private void resetHeaderHeight() { int height = mHeaderView.getVisiableHeight(); if (height == 0) // not visible. return; // refreshing and header isn't shown fully. do nothing. if (mPullRefreshing && height <= mHeaderViewHeight) { return; } int finalHeight = 0; // default: scroll back to dismiss header. // is refreshing, just scroll back to show all the header. if (mPullRefreshing && height > mHeaderViewHeight) { finalHeight = mHeaderViewHeight; } mScrollBack = SCROLLBACK_HEADER; mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION); // trigger computeScroll invalidate(); } private void updateFooterHeight(float delta) { int height = mFooterView.getBottomMargin() + (int) delta; if (mEnablePullLoad && !mPullLoading) { if (height > PULL_LOAD_MORE_DELTA) { // height enough to invoke load // more. mFooterView.setState(XListViewFooter.STATE_READY); } else { mFooterView.setState(XListViewFooter.STATE_NORMAL); } } mFooterView.setBottomMargin(height); // setSelection(mTotalItemCount - 1); // scroll to bottom } private void resetFooterHeight() { int bottomMargin = mFooterView.getBottomMargin(); if (bottomMargin > 0) { mScrollBack = SCROLLBACK_FOOTER; mScroller.startScroll(0, bottomMargin, 0, -bottomMargin, SCROLL_DURATION); invalidate(); } } private void startLoadMore() { mPullLoading = true; mFooterView.setState(XListViewFooter.STATE_LOADING); if (mListViewListener != null) { mListViewListener.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 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) { // the first item is showing, header has shown or pull down. updateHeaderHeight(deltaY / OFFSET_RADIO); invokeOnScrolling(); } else if (getLastVisiblePosition() == mTotalItemCount - 1 && (mFooterView.getBottomMargin() > 0 || deltaY < 0)) { // last item, already pulled up or want to pull up. updateFooterHeight(-deltaY / OFFSET_RADIO); } break; default: mLastY = -1; // reset if (getFirstVisiblePosition() == 0) { // invoke refresh if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mPullRefreshing = true; mHeaderView.setState(XListViewHeader.STATE_REFRESHING); if (mListViewListener != null) { mListViewListener.onRefresh(); } } resetHeaderHeight(); } else if (getLastVisiblePosition() == mTotalItemCount - 1) { // invoke load more. if (mEnablePullLoad && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA) { startLoadMore(); } resetFooterHeight(); } break; } return super.onTouchEvent(ev); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { if (mScrollBack == SCROLLBACK_HEADER) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); } else { mFooterView.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 setXListViewListener(IXListViewListener l) { mListViewListener = l; } /** * you can listen ListView.OnScrollListener or this one. it will invoke * onXScrolling when header/footer scroll back. */ public interface OnXScrollListener extends OnScrollListener { public void onXScrolling(View view); } /** * implements this interface to get refresh/load more event. */ public interface IXListViewListener { public void onRefresh(); public void onLoadMore(); } }
package com.curiousby.demoxlistview.xlistview; import java.text.SimpleDateFormat; import java.util.Date; import android.annotation.SuppressLint; /** * 时间工具类 * * @author way * */ @SuppressLint("SimpleDateFormat") public class TimeUtil { public static String getTime(long time) { SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm"); return format.format(new Date(time)); } public static String getHourAndMin(long time) { SimpleDateFormat format = new SimpleDateFormat("HH:mm"); return format.format(new Date(time)); } public static String getChatTime(long timesamp) { String result = ""; SimpleDateFormat sdf = new SimpleDateFormat("dd"); Date today = new Date(System.currentTimeMillis()); Date otherDay = new Date(timesamp); int temp = Integer.parseInt(sdf.format(today)) - Integer.parseInt(sdf.format(otherDay)); switch (temp) { case 0: result = "今天 " + getHourAndMin(timesamp); break; case 1: result = "昨天 " + getHourAndMin(timesamp); break; case 2: result = "前天 " + getHourAndMin(timesamp); break; default: // result = temp + "天前 "; result = getTime(timesamp); break; } return result; } }
/** * @file XListView.java * @package me.maxwin.view * @create Mar 18, 2012 6:28:41 PM * @author Maxwin * @description An ListView support (a) Pull down to refresh, (b) Pull up to load more. * Implement IXListViewListener, and see stopRefresh() / stopLoadMore(). */ package com.curiousby.demoxlistview.xlistview; import com.curiousby.demoxlistview.R; import android.content.Context; import android.util.AttributeSet; 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; //com.curiousby.demoxlistview.xlistview.MsgListView public class MsgListView extends ListView implements OnScrollListener { private float mLastY = -1; // save event y private Scroller mScroller; // used for scroll back private OnScrollListener mScrollListener; // user's scroll listener // the interface to trigger refresh and load more. private IXListViewListener mListViewListener; // -- header view private MsgHeader mHeaderView; // header view content, use it to calculate the Header's height. And hide it // when disable pull refresh. private RelativeLayout mHeaderViewContent; // private TextView mHeaderTimeView; private int mHeaderViewHeight; // header view's height private boolean mEnablePullRefresh = true;// private boolean mPullRefreshing = false; // is refreashing. // -- footer view private XListViewFooter mFooterView; private boolean mEnablePullLoad; private boolean mPullLoading; private boolean mIsFooterReady = false; // total list items, used to detect is at the bottom of listview. private int mTotalItemCount; // for mScroller, scroll back from header or footer. private int mScrollBack; private final static int SCROLLBACK_HEADER = 0; private final static int SCROLLBACK_FOOTER = 1; private final static int SCROLL_DURATION = 400; // scroll back duration private final static int PULL_LOAD_MORE_DELTA = 50; // when pull up >= 50px // at bottom, trigger // load more. private final static float OFFSET_RADIO = 1.8f; // support iOS like pull // feature. /** * @param context */ public MsgListView(Context context) { super(context); initWithContext(context); } public MsgListView(Context context, AttributeSet attrs) { super(context, attrs); initWithContext(context); } public MsgListView(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 event, and it will dispatch the event to // user's listener (as a proxy). super.setOnScrollListener(this); // init header view mHeaderView = new MsgHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView .findViewById(R.id.msg_header_content); // mHeaderTimeView = (TextView) mHeaderView // .findViewById(R.id.xlistview_header_time); addHeaderView(mHeaderView); // init footer view mFooterView = new XListViewFooter(context); // init header height mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); } @Override public void setAdapter(ListAdapter adapter) { // make sure XListViewFooter is the last footer view, and only add once. if (mIsFooterReady == false) { mIsFooterReady = true; addFooterView(mFooterView); } super.setAdapter(adapter); } /** * enable or disable pull down refresh feature. * * @param enable */ public void setPullRefreshEnable(boolean enable) { mEnablePullRefresh = enable; if (!mEnablePullRefresh) { // disable, hide the content mHeaderViewContent.setVisibility(View.INVISIBLE); } else { mHeaderViewContent.setVisibility(View.VISIBLE); } } /** * enable or disable pull up load more feature. * * @param enable */ public void setPullLoadEnable(boolean enable) { mEnablePullLoad = enable; if (!mEnablePullLoad) { mFooterView.hide(); mFooterView.setOnClickListener(null); } else { mPullLoading = false; mFooterView.show(); mFooterView.setState(XListViewFooter.STATE_NORMAL); // both "pull up" and "click" will invoke load more. mFooterView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startLoadMore(); } }); } } /** * stop refresh, reset header view. */ public void stopRefresh() { if (mPullRefreshing == true) { mPullRefreshing = false; resetHeaderHeight(); } } /** * stop load more, reset footer view. */ public void stopLoadMore() { if (mPullLoading == true) { mPullLoading = false; mFooterView.setState(XListViewFooter.STATE_NORMAL); } } /** * set last refresh time * * @param time */ // public void setRefreshTime(String time) { // mHeaderTimeView.setText(time); // } // // public void setRefreshTime(long time) { // mHeaderTimeView.setText(TimeUtil.getChatTime(time)); // } private void invokeOnScrolling() { if (mScrollListener instanceof OnXScrollListener) { OnXScrollListener l = (OnXScrollListener) mScrollListener; l.onXScrolling(this); } } private void updateHeaderHeight(float delta) { mHeaderView.setVisiableHeight((int) delta + mHeaderView.getVisiableHeight()); if (mEnablePullRefresh && !mPullRefreshing) { // 未处于刷新状态,更新箭头 if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mHeaderView.setState(XListViewHeader.STATE_READY); } else { mHeaderView.setState(XListViewHeader.STATE_NORMAL); } } setSelection(0); // scroll to top each time } /** * reset header view's height. */ private void resetHeaderHeight() { int height = mHeaderView.getVisiableHeight(); if (height == 0) // not visible. return; // refreshing and header isn't shown fully. do nothing. if (mPullRefreshing && height <= mHeaderViewHeight) { return; } int finalHeight = 0; // default: scroll back to dismiss header. // is refreshing, just scroll back to show all the header. if (mPullRefreshing && height > mHeaderViewHeight) { finalHeight = mHeaderViewHeight; } mScrollBack = SCROLLBACK_HEADER; mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION); // trigger computeScroll invalidate(); } private void updateFooterHeight(float delta) { int height = mFooterView.getBottomMargin() + (int) delta; if (mEnablePullLoad && !mPullLoading) { if (height > PULL_LOAD_MORE_DELTA) { // height enough to invoke load // more. mFooterView.setState(XListViewFooter.STATE_READY); } else { mFooterView.setState(XListViewFooter.STATE_NORMAL); } } mFooterView.setBottomMargin(height); // setSelection(mTotalItemCount - 1); // scroll to bottom } private void resetFooterHeight() { int bottomMargin = mFooterView.getBottomMargin(); if (bottomMargin > 0) { mScrollBack = SCROLLBACK_FOOTER; mScroller.startScroll(0, bottomMargin, 0, -bottomMargin, SCROLL_DURATION); invalidate(); } } private void startLoadMore() { mPullLoading = true; mFooterView.setState(XListViewFooter.STATE_LOADING); if (mListViewListener != null) { mListViewListener.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 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) { // the first item is showing, header has shown or pull down. updateHeaderHeight(deltaY / OFFSET_RADIO); invokeOnScrolling(); } else if (getLastVisiblePosition() == mTotalItemCount - 1 && (mFooterView.getBottomMargin() > 0 || deltaY < 0)) { // last item, already pulled up or want to pull up. updateFooterHeight(-deltaY / OFFSET_RADIO); } break; default: mLastY = -1; // reset if (getFirstVisiblePosition() == 0) { // invoke refresh if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mPullRefreshing = true; mHeaderView.setState(XListViewHeader.STATE_REFRESHING); if (mListViewListener != null) { mListViewListener.onRefresh(); } } resetHeaderHeight(); } else if (getLastVisiblePosition() == mTotalItemCount - 1) { // invoke load more. if (mEnablePullLoad && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA) { startLoadMore(); } resetFooterHeight(); } break; } return super.onTouchEvent(ev); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { if (mScrollBack == SCROLLBACK_HEADER) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); } else { mFooterView.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 setXListViewListener(IXListViewListener l) { mListViewListener = l; } /** * you can listen ListView.OnScrollListener or this one. it will invoke * onXScrolling when header/footer scroll back. */ public interface OnXScrollListener extends OnScrollListener { public void onXScrolling(View view); } /** * implements this interface to get refresh/load more event. */ public interface IXListViewListener { public void onRefresh(); public void onLoadMore(); } }
/** * @file XListViewHeader.java * @create Apr 18, 2012 5:22:27 PM * @author Maxwin * @description XListView's header */ package com.curiousby.demoxlistview.xlistview; import com.curiousby.demoxlistview.R; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; public class MsgHeader extends LinearLayout { private LinearLayout mContainer; private ProgressBar mProgressBar; private TextView mHintTextView; private int mState = STATE_NORMAL; public final static int STATE_NORMAL = 0; public final static int STATE_READY = 1; public final static int STATE_REFRESHING = 2; public MsgHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public MsgHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { // 初始情况,设置下拉刷新view高度为0 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate( R.layout.message_header, null); addView(mContainer, lp); setGravity(Gravity.BOTTOM); mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview); mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar); } public void setState(int state) { if (state == mState) return; if (state == STATE_REFRESHING) { // 显示进度 mProgressBar.setVisibility(View.VISIBLE); } else { // 显示箭头图片 mProgressBar.setVisibility(View.INVISIBLE); } switch (state) { case STATE_NORMAL: if (mState == STATE_READY) { } if (mState == STATE_REFRESHING) { } mHintTextView.setVisibility(View.VISIBLE); mHintTextView.setText("显示更多消息"); break; case STATE_READY: if (mState != STATE_READY) { mHintTextView.setVisibility(View.VISIBLE); mHintTextView.setText("释放即可显示"); } break; case STATE_REFRESHING: // mHintTextView.setText(R.string.xlistview_header_hint_loading); mHintTextView.setVisibility(View.GONE); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getVisiableHeight() { return mContainer.getHeight(); } }
package com.curiousby.demoxlistview.xlistview; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnGroupClickListener; public class IphoneTreeView extends ExpandableListView implements OnScrollListener, OnGroupClickListener { public IphoneTreeView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); registerListener(); } public IphoneTreeView(Context context, AttributeSet attrs) { super(context, attrs); registerListener(); } public IphoneTreeView(Context context) { super(context); registerListener(); } /** * Adapter 接口 . 列表必须实现此接口 . */ public interface IphoneTreeHeaderAdapter { public static final int PINNED_HEADER_GONE = 0; public static final int PINNED_HEADER_VISIBLE = 1; public static final int PINNED_HEADER_PUSHED_UP = 2; /** * 获取 Header 的状态 * * @param groupPosition * @param childPosition * @return * PINNED_HEADER_GONE,PINNED_HEADER_VISIBLE,PINNED_HEADER_PUSHED_UP * 其中之一 */ int getTreeHeaderState(int groupPosition, int childPosition); /** * 配置 QQHeader, 让 QQHeader 知道显示的内容 * * @param header * @param groupPosition * @param childPosition * @param alpha */ void configureTreeHeader(View header, int groupPosition, int childPosition, int alpha); /** * 设置组按下的状态 * * @param groupPosition * @param status */ void onHeadViewClick(int groupPosition, int status); /** * 获取组按下的状态 * * @param groupPosition * @return */ int getHeadViewClickStatus(int groupPosition); } private static final int MAX_ALPHA = 255; private IphoneTreeHeaderAdapter mAdapter; /** * 用于在列表头显示的 View,mHeaderViewVisible 为 true 才可见 */ private View mHeaderView; /** * 列表头是否可见 */ private boolean mHeaderViewVisible; private int mHeaderViewWidth; private int mHeaderViewHeight; public void setHeaderView(View view) { mHeaderView = view; AbsListView.LayoutParams lp = new AbsListView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); view.setLayoutParams(lp); if (mHeaderView != null) { setFadingEdgeLength(0); } requestLayout(); } private void registerListener() { setOnScrollListener(this); setOnGroupClickListener(this); } /** * 点击 HeaderView 触发的事件 */ private void headerViewClick() { long packedPosition = getExpandableListPosition(this .getFirstVisiblePosition()); int groupPosition = ExpandableListView .getPackedPositionGroup(packedPosition); if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) { this.collapseGroup(groupPosition); mAdapter.onHeadViewClick(groupPosition, 0); } else { this.expandGroup(groupPosition); mAdapter.onHeadViewClick(groupPosition, 1); } this.setSelectedGroup(groupPosition); } private float mDownX; private float mDownY; /** * 如果 HeaderView 是可见的 , 此函数用于判断是否点击了 HeaderView, 并对做相应的处理 , 因为 HeaderView * 是画上去的 , 所以设置事件监听是无效的 , 只有自行控制 . */ @Override public boolean onTouchEvent(MotionEvent ev) { if (mHeaderViewVisible) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = ev.getX(); mDownY = ev.getY(); if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) { return true; } break; case MotionEvent.ACTION_UP: float x = ev.getX(); float y = ev.getY(); float offsetX = Math.abs(x - mDownX); float offsetY = Math.abs(y - mDownY); // 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触 // 发 headerClick() if (x <= mHeaderViewWidth && y <= mHeaderViewHeight && offsetX <= mHeaderViewWidth && offsetY <= mHeaderViewHeight) { if (mHeaderView != null) { headerViewClick(); } return true; } break; default: break; } } return super.onTouchEvent(ev); } @Override public void setAdapter(ExpandableListAdapter adapter) { super.setAdapter(adapter); mAdapter = (IphoneTreeHeaderAdapter) adapter; } /** * * 点击了 Group 触发的事件 , 要根据根据当前点击 Group 的状态来 */ @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { if (mAdapter.getHeadViewClickStatus(groupPosition) == 0) { mAdapter.onHeadViewClick(groupPosition, 1); parent.expandGroup(groupPosition); parent.setSelectedGroup(groupPosition); } else if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) { mAdapter.onHeadViewClick(groupPosition, 0); parent.collapseGroup(groupPosition); } // 返回 true 才可以弹回第一行 , 不知道为什么 return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHeaderView != null) { measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); mHeaderViewWidth = mHeaderView.getMeasuredWidth(); mHeaderViewHeight = mHeaderView.getMeasuredHeight(); } } private int mOldState = -1; @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); final long flatPostion = getExpandableListPosition(getFirstVisiblePosition()); final int groupPos = ExpandableListView .getPackedPositionGroup(flatPostion); final int childPos = ExpandableListView .getPackedPositionChild(flatPostion); int state = mAdapter.getTreeHeaderState(groupPos, childPos); if (mHeaderView != null && mAdapter != null && state != mOldState) { mOldState = state; mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); } configureHeaderView(groupPos, childPos); } public void configureHeaderView(int groupPosition, int childPosition) { if (mHeaderView == null || mAdapter == null || ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) { return; } int state = mAdapter.getTreeHeaderState(groupPosition, childPosition); switch (state) { case IphoneTreeHeaderAdapter.PINNED_HEADER_GONE: { mHeaderViewVisible = false; break; } case IphoneTreeHeaderAdapter.PINNED_HEADER_VISIBLE: { mAdapter.configureTreeHeader(mHeaderView, groupPosition, childPosition, MAX_ALPHA); if (mHeaderView.getTop() != 0) { mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); } mHeaderViewVisible = true; break; } case IphoneTreeHeaderAdapter.PINNED_HEADER_PUSHED_UP: { View firstView = getChildAt(0); int bottom = firstView.getBottom(); // intitemHeight = firstView.getHeight(); int headerHeight = mHeaderView.getHeight(); int y; int alpha; if (bottom < headerHeight) { y = (bottom - headerHeight); alpha = MAX_ALPHA * (headerHeight + y) / headerHeight; } else { y = 0; alpha = MAX_ALPHA; } mAdapter.configureTreeHeader(mHeaderView, groupPosition, childPosition, alpha); if (mHeaderView.getTop() != y) { mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y); } mHeaderViewVisible = true; break; } } } @Override /** * 列表界面更新时调用该方法(如滚动时) */ protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mHeaderViewVisible) { // 分组栏是直接绘制到界面中,而不是加入到ViewGroup中 drawChild(canvas, mHeaderView, getDrawingTime()); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { final long flatPos = getExpandableListPosition(firstVisibleItem); int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos); int childPosition = ExpandableListView.getPackedPositionChild(flatPos); configureHeaderView(groupPosition, childPosition); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } }
捐助开发者
在兴趣的驱动下,写一个免费
的东西,有欣喜,也还有汗水,希望你喜欢我的作品,同时也能支持一下。 当然,有钱捧个钱场(右上角的爱心标志,支持支付宝和PayPal捐助),没钱捧个人场,谢谢各位。
谢谢您的赞助,我会做的更好!
相关推荐
这个“Android XListview实现上拉刷新下拉加载功能”的源码Demo就是针对这一需求的实例,特别适合学生进行毕业设计学习。 XListView是一个Android开源项目,它扩展了Android自带的ListView,添加了上拉刷新和下拉...
XlistView是一个在编程领域,特别是移动开发中常见的组件,主要用于展示列表数据。在这个主题下,我们将探讨XlistView的基本概念、应用场景、使用方法以及如何通过提供的XlistViewDemo进行实践学习。 首先,...
Android XListView是一个专为ListView设计的开源库,它提供了简单的集成方式来实现这两种常见功能。相比其他通用的刷新框架如SwipeRefreshLayout和PullToRefresh,XListView更专注于ListView,因此在使用时更为简洁...
XListView是Android开源项目中的一个明星组件,它的核心功能是实现了PullToRefresh(上拉刷新)和LoadMore(下拉加载)的效果。在用户滚动到ListView的顶部时,可以通过上拉手势触发刷新操作,更新数据;当用户滚动...
源码参考,欢迎下载
在Android开发中,"弹性ListView"(Bounce ListView)是一种增强型的ListView,它提供了类似iOS设备上列表视图的滚动回弹效果。这种效果在用户滚动到列表顶部或底部时,会有一个自然的反弹动作,增加了用户体验的...
原XListView参考链接:https://github.com/Maxwin-z/XListView-Android XListView使用示例 设置XListView相关属性 mListView = (XListView) findViewById(R.id.list_view); mListView.setPullRefreshEnable(true); ...
SwipeLayout是一种常见的Android UI组件,它实现了类似QQ消息列表中的侧滑删除功能。这个功能让用户能够通过在列表项上向左或向右滑动来触发一个操作,如删除、标记或者更多选项。在Android开发中,实现这样的效果...
XListView是一个经典的库,它提供了这些特性,但随着Android系统的演进,RecyclerView逐渐成为列表视图的标准组件。因此,将XListView转换为RecyclerView并保持相同的刷新和加载功能变得至关重要。 本篇将详细介绍...
XlistView是Android平台上一个流行且功能强大的列表视图组件,它基于Android原生的ListView进行扩展,提供了更丰富的交互效果和定制能力。要实现自动下拉刷新,我们需要集成一个下拉刷新库,如SwipeRefreshLayout。...
这个功能让用户在浏览数据列表时能够轻松获取最新内容,而XlistView是Android平台一个常用的下拉刷新组件。本文将深入探讨如何改造XlistView以实现自动下拉刷新,并介绍相关的关键知识点。 首先,我们需要理解下拉...
XListView是Android社区中一个非常流行的自定义ListView库,它提供了强大的下拉刷新和上拉加载更多的功能,同时支持自定义布局,使得开发者可以灵活地定制界面样式。 XListView的核心原理是基于Android的...
本篇文章将详细介绍如何在Android项目中集成XListView,实现这些功能。 XListView是一个自定义的ListView,它集成了上拉刷新和下拉加载更多的特性,同时也支持手势滑动删除。在实现这些功能之前,你需要确保你的...
XListView是一款在Android平台上广泛使用的开源组件,它集成了下拉刷新和上拉加载更多的功能,为开发者提供了方便、高效的列表视图解决方案。这款组件最初由xiemengjun开发,后来在GitHub上开源,受到了众多开发者的...
首先,`XListView`是一个开源项目,它继承自Android原生的ListView,并添加了上拉加载和下拉刷新的功能。开发者可以在不改变原有ListView使用习惯的基础上,轻松实现这些高级特性。它的主要优点在于简化了复杂滚动...
此时,XListView应运而生,它是一款强大的、开源的Android列表视图控件,集成了下拉刷新和上拉加载更多的功能。 XListView是基于ListView进行扩展的,其核心功能在于提供了下拉刷新和上拉加载更多的交互模式,使得...
【Android应用源码之XListView--master.rar】是一个包含了Android应用源码的压缩包,主要聚焦于一个名为XListView的自定义组件。XListView是Android平台上的一个可滚动列表视图,它扩展了标准的ListView组件,提供了...
`XListView`是一个专为这个目的设计的Android自定义视图组件,它结合了下拉刷新和上拉加载更多的功能。在本文中,我们将深入探讨`XListView`的核心概念、用法以及它如何帮助开发者提升用户体验。 `XListView`最初由...