您的位置:首页 > 移动开发 > Android开发

Android ExpandableListView 仿QQ列表页面,title永远在上面,带下拉刷新,上拉加载。

2015-05-06 16:03 465 查看
最近公司有项目需求要求实现这功能,费了一天时间弄了下,决定共享出去。展开后header会固定在最上方











  首先网上下载PullToRefreshView    具体内容如下:

package com.cdvcloud.ugc_1.view.refresh;

import android.content.Context;

import android.util.AttributeSet;

import android.view.LayoutInflater;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;

import android.view.animation.LinearInterpolator;

import android.view.animation.RotateAnimation;

import android.widget.AdapterView;

import android.widget.ImageView;

import android.widget.LinearLayout;

import android.widget.ProgressBar;

import android.widget.ScrollView;

import android.widget.TextView;

import com.cdvcloud.ugc_1.R;

public class PullToRefreshView extends LinearLayout {

// private static final String TAG = "PullToRefreshView";

// refresh states

private static final int PULL_TO_REFRESH = 2;

private static final int RELEASE_TO_REFRESH = 3;

private static final int REFRESHING = 4;

// pull state

private static final int PULL_UP_STATE = 0;

private static final int PULL_DOWN_STATE = 1;

/**

* last y

*/

private int mLastMotionY;

/**

* lock

*/

// private boolean mLock;

/**

* header view

*/

private View mHeaderView;

/**

* footer view

*/

private View mFooterView;

/**

* list or grid

*/

private AdapterView<?> mAdapterView;

/**

* scrollview

*/

private ScrollView mScrollView;

/**

* header view height

*/

private int mHeaderViewHeight;

/**

* footer view height

*/

private int mFooterViewHeight;

/**

* header view image

*/

private ImageView mHeaderImageView;

/**

* footer view image

*/

private ImageView mFooterImageView;

/**

* header tip text

*/

private TextView mHeaderTextView;

/**

* footer tip text

*/

private TextView mFooterTextView;

/**

* header refresh time

*/

private TextView mHeaderUpdateTextView;

/**

* footer refresh time

*/

// private TextView mFooterUpdateTextView;

/**

* header progress bar

*/

private ProgressBar mHeaderProgressBar;

/**

* footer progress bar

*/

private ProgressBar mFooterProgressBar;

/**

* layout inflater

*/

private LayoutInflater mInflater;

/**

* header view current state

*/

private int mHeaderState;

/**

* footer view current state

*/

private int mFooterState;

/**

* pull state,pull up or pull down;PULL_UP_STATE or PULL_DOWN_STATE

*/

private int mPullState;

/**

* 变为向下的箭头,改变箭头方向

*/

private RotateAnimation mFlipAnimation;

/**

* 变为逆向的箭头,旋转

*/

private RotateAnimation mReverseFlipAnimation;

/**

* footer refresh listener

*/

private OnFooterRefreshListener mOnFooterRefreshListener;

/**

* footer refresh listener

*/

private OnHeaderRefreshListener mOnHeaderRefreshListener;

/**

* last update time

*/

// private String mLastUpdateTime;

public PullToRefreshView(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

public PullToRefreshView(Context context) {

super(context);

init();

}

/**

* init

*

* @param context

*/

private void init() {

//需要设置成vertical

setOrientation(LinearLayout.VERTICAL);

// Load all of the animations we need in code rather than through XML

mFlipAnimation = new RotateAnimation(0, -180,

RotateAnimation.RELATIVE_TO_SELF, 0.5f,

RotateAnimation.RELATIVE_TO_SELF, 0.5f);

mFlipAnimation.setInterpolator(new LinearInterpolator());

mFlipAnimation.setDuration(250);

mFlipAnimation.setFillAfter(true);

mReverseFlipAnimation = new RotateAnimation(-180, 0,

RotateAnimation.RELATIVE_TO_SELF, 0.5f,

RotateAnimation.RELATIVE_TO_SELF, 0.5f);

mReverseFlipAnimation.setInterpolator(new LinearInterpolator());

mReverseFlipAnimation.setDuration(250);

mReverseFlipAnimation.setFillAfter(true);

mInflater = LayoutInflater.from(getContext());

// header view 在此添加,保证是第一个添加到linearlayout的最上端

addHeaderView();

}

private void addHeaderView() {

// header view

mHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);

mHeaderImageView = (ImageView) mHeaderView

.findViewById(R.id.pull_to_refresh_image);

mHeaderTextView = (TextView) mHeaderView

.findViewById(R.id.pull_to_refresh_text);

mHeaderUpdateTextView = (TextView) mHeaderView

.findViewById(R.id.pull_to_refresh_updated_at);

mHeaderProgressBar = (ProgressBar) mHeaderView

.findViewById(R.id.pull_to_refresh_progress);

// header layout

measureView(mHeaderView);

mHeaderViewHeight = mHeaderView.getMeasuredHeight();

LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,

mHeaderViewHeight);

// 设置topMargin的值为负的header View高度,即将其隐藏在最上方

params.topMargin = -(mHeaderViewHeight);

// mHeaderView.setLayoutParams(params1);

addView(mHeaderView, params);

}

private void addFooterView() {

// footer view

mFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);

mFooterImageView = (ImageView) mFooterView

.findViewById(R.id.pull_to_load_image);

mFooterTextView = (TextView) mFooterView

.findViewById(R.id.pull_to_load_text);

mFooterProgressBar = (ProgressBar) mFooterView

.findViewById(R.id.pull_to_load_progress);

// footer layout

measureView(mFooterView);

mFooterViewHeight = mFooterView.getMeasuredHeight();

LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,

mFooterViewHeight);

// int top = getHeight();

// params.topMargin

// =getHeight();//在这里getHeight()==0,但在onInterceptTouchEvent()方法里getHeight()已经有值了,不再是0;

// getHeight()什么时候会赋值,稍候再研究一下

// 由于是线性布局可以直接添加,只要AdapterView的高度是MATCH_PARENT,那么footer view就会被添加到最后,并隐藏

addView(mFooterView, params);

}

@Override

protected void onFinishInflate() {

super.onFinishInflate();

// footer view 在此添加保证添加到linearlayout中的最后

addFooterView();

initContentAdapterView();

}

/**

* init AdapterView like ListView,GridView and so on;or init ScrollView

*

*/

private void initContentAdapterView() {

int count = getChildCount();

if (count < 3) {

throw new IllegalArgumentException(

"This layout must contain 3 child views,and AdapterView or ScrollView must in the second position!");

}

View view = null;

for (int i = 0; i < count - 1; ++i) {

view = getChildAt(i);

if (view instanceof AdapterView<?>) {

mAdapterView = (AdapterView<?>) view;

}

if (view instanceof ScrollView) {

// finish later

mScrollView = (ScrollView) view;

}

}

if (mAdapterView == null && mScrollView == null) {

throw new IllegalArgumentException(

"must contain a AdapterView or ScrollView in this layout!");

}

}

private void measureView(View child) {

ViewGroup.LayoutParams p = child.getLayoutParams();

if (p == null) {

p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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);

}

@Override

public boolean onInterceptTouchEvent(MotionEvent e) {

int y = (int) e.getRawY();

switch (e.getAction()) {

case MotionEvent.ACTION_DOWN:

// 首先拦截down事件,记录y坐标

mLastMotionY = y;

break;

case MotionEvent.ACTION_MOVE:

// deltaY > 0 是向下运动,< 0是向上运动

int deltaY = y - mLastMotionY;

if (isRefreshViewScroll(deltaY)) {

return true;

}

break;

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_CANCEL:

break;

}

return false;

}

/*

* 如果在onInterceptTouchEvent()方法中没有拦截(即onInterceptTouchEvent()方法中 return

* false)则由PullToRefreshView 的子View来处理;否则由下面的方法来处理(即由PullToRefreshView自己来处理)

*/

@Override

public boolean onTouchEvent(MotionEvent event) {

// if (mLock) {

// return true;

// }

int y = (int) event.getRawY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

// onInterceptTouchEvent已经记录

// mLastMotionY = y;

break;

case MotionEvent.ACTION_MOVE:

int deltaY = y - mLastMotionY;

if (mPullState == PULL_DOWN_STATE) {//执行下拉

headerPrepareToRefresh(deltaY);

// setHeaderPadding(-mHeaderViewHeight);

} else if (mPullState == PULL_UP_STATE) {//执行上拉

footerPrepareToRefresh(deltaY);

}

mLastMotionY = y;

break;

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_CANCEL:

int topMargin = getHeaderTopMargin();

if (mPullState == PULL_DOWN_STATE) {

if (topMargin >= 0) {

// 开始刷新

headerRefreshing();

} else {

// 还没有执行刷新,重新隐藏

setHeaderTopMargin(-mHeaderViewHeight);

}

} else if (mPullState == PULL_UP_STATE) {

if (Math.abs(topMargin) >= mHeaderViewHeight

+ mFooterViewHeight) {

// 开始执行footer 刷新

footerRefreshing();

} else {

// 还没有执行刷新,重新隐藏

setHeaderTopMargin(-mHeaderViewHeight);

}

}

break;

}

return super.onTouchEvent(event);

}

/**

* 是否应该到了父View,即PullToRefreshView滑动

*

* @param deltaY

* , deltaY > 0 是向下运动,< 0是向上运动

* @return

*/

private boolean isRefreshViewScroll(int deltaY) {

if (mHeaderState == REFRESHING || mFooterState == REFRESHING) {

return false;

}

//对于ListView和GridView

if (mAdapterView != null) {

// 子view(ListView or GridView)滑动到最顶端

if (deltaY > 0) {

View child = mAdapterView.getChildAt(0);

if (child == null) {

// 如果mAdapterView中没有数据,不拦截

return false;

}

if (mAdapterView.getFirstVisiblePosition() == 0

&& child.getTop() == 0) {

mPullState = PULL_DOWN_STATE;

return true;

}

int top = child.getTop();

int padding = mAdapterView.getPaddingTop();

if (mAdapterView.getFirstVisiblePosition() == 0

&& Math.abs(top - padding) <= 8) {//这里之前用3可以判断,但现在不行,还没找到原因

mPullState = PULL_DOWN_STATE;

return true;

}

} else if (deltaY < 0) {

View lastChild = mAdapterView.getChildAt(mAdapterView

.getChildCount() - 1);

if (lastChild == null) {

// 如果mAdapterView中没有数据,不拦截

return false;

}

// 最后一个子view的Bottom小于父View的高度说明mAdapterView的数据没有填满父view,

// 等于父View的高度说明mAdapterView已经滑动到最后

if (lastChild.getBottom() <= getHeight()

&& mAdapterView.getLastVisiblePosition() == mAdapterView

.getCount() - 1) {

mPullState = PULL_UP_STATE;

return true;

}

}

}

// 对于ScrollView

if (mScrollView != null) {

// 子scroll view滑动到最顶端

View child = mScrollView.getChildAt(0);

if (deltaY > 0 && mScrollView.getScrollY() == 0) {

mPullState = PULL_DOWN_STATE;

return true;

} else if (deltaY < 0

&& child.getMeasuredHeight() <= getHeight()

+ mScrollView.getScrollY()) {

mPullState = PULL_UP_STATE;

return true;

}

}

return false;

}

/**

* header 准备刷新,手指移动过程,还没有释放

*

* @param deltaY

* ,手指滑动的距离

*/

private void headerPrepareToRefresh(int deltaY) {

int newTopMargin = changingHeaderViewTopMargin(deltaY);

// 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态

if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {

mHeaderTextView.setText(R.string.pull_to_refresh_release_label);

mHeaderUpdateTextView.setVisibility(View.VISIBLE);

mHeaderImageView.clearAnimation();

mHeaderImageView.startAnimation(mFlipAnimation);

mHeaderState = RELEASE_TO_REFRESH;

} else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放

mHeaderImageView.clearAnimation();

mHeaderImageView.startAnimation(mFlipAnimation);

// mHeaderImageView.

mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);

mHeaderState = PULL_TO_REFRESH;

}

}

/**

* footer 准备刷新,手指移动过程,还没有释放 移动footer view高度同样和移动header view

* 高度是一样,都是通过修改header view的topmargin的值来达到

*

* @param deltaY

* ,手指滑动的距离

*/

private void footerPrepareToRefresh(int deltaY) {

int newTopMargin = changingHeaderViewTopMargin(deltaY);

// 如果header view topMargin 的绝对值大于或等于header + footer 的高度

// 说明footer view 完全显示出来了,修改footer view 的提示状态

if (Math.abs(newTopMargin) >= (mHeaderViewHeight + mFooterViewHeight)

&& mFooterState != RELEASE_TO_REFRESH) {

mFooterTextView

.setText(R.string.pull_to_refresh_footer_release_label);

mFooterImageView.clearAnimation();

mFooterImageView.startAnimation(mFlipAnimation);

mFooterState = RELEASE_TO_REFRESH;

} else if (Math.abs(newTopMargin) < (mHeaderViewHeight + mFooterViewHeight)) {

mFooterImageView.clearAnimation();

mFooterImageView.startAnimation(mFlipAnimation);

mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);

mFooterState = PULL_TO_REFRESH;

}

}

/**

* 修改Header view top margin的值

*

* @param deltaY

*/

private int changingHeaderViewTopMargin(int deltaY) {

LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();

float newTopMargin = params.topMargin + deltaY * 0.3f;

//这里对上拉做一下限制,因为当前上拉后然后不释放手指直接下拉,会把下拉刷新给触发了,感谢网友yufengzungzhe的指出

//表示如果是在上拉后一段距离,然后直接下拉

if(deltaY>0&&mPullState == PULL_UP_STATE&&Math.abs(params.topMargin) <= mHeaderViewHeight){

return params.topMargin;

}

//同样地,对下拉做一下限制,避免出现跟上拉操作时一样的bug

if(deltaY<0&&mPullState == PULL_DOWN_STATE&&Math.abs(params.topMargin)>=mHeaderViewHeight){

return params.topMargin;

}

params.topMargin = (int) newTopMargin;

mHeaderView.setLayoutParams(params);

invalidate();

return params.topMargin;

}

/**

* header refreshing

*

*/

private void headerRefreshing() {

mHeaderState = REFRESHING;

setHeaderTopMargin(0);

mHeaderImageView.setVisibility(View.GONE);

mHeaderImageView.clearAnimation();

mHeaderImageView.setImageDrawable(null);

mHeaderProgressBar.setVisibility(View.VISIBLE);

mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);

if (mOnHeaderRefreshListener != null) {

mOnHeaderRefreshListener.onHeaderRefresh(this);

}

}

/**

* footer refreshing

*

*/

private void footerRefreshing() {

mFooterState = REFRESHING;

int top = mHeaderViewHeight + mFooterViewHeight;

setHeaderTopMargin(-top);

mFooterImageView.setVisibility(View.GONE);

mFooterImageView.clearAnimation();

mFooterImageView.setImageDrawable(null);

mFooterProgressBar.setVisibility(View.VISIBLE);

mFooterTextView

.setText(R.string.pull_to_refresh_footer_refreshing_label);

if (mOnFooterRefreshListener != null) {

mOnFooterRefreshListener.onFooterRefresh(this);

}

}

/**

* 设置header view 的topMargin的值

*

* @param topMargin

* ,为0时,说明header view 刚好完全显示出来; 为-mHeaderViewHeight时,说明完全隐藏了

*/

private void setHeaderTopMargin(int topMargin) {

LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();

params.topMargin = topMargin;

mHeaderView.setLayoutParams(params);

invalidate();

}

/**

* header view 完成更新后恢复初始状态

*

*/

public void onHeaderRefreshComplete() {

setHeaderTopMargin(-mHeaderViewHeight);

mHeaderImageView.setVisibility(View.VISIBLE);

mHeaderImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow);

mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);

mHeaderProgressBar.setVisibility(View.GONE);

// mHeaderUpdateTextView.setText("");

mHeaderState = PULL_TO_REFRESH;

}

/**

* Resets the list to a normal state after a refresh.

*

* @param lastUpdated

* Last updated at.

*/

public void onHeaderRefreshComplete(CharSequence lastUpdated) {

setLastUpdated(lastUpdated);

onHeaderRefreshComplete();

}

/**

* footer view 完成更新后恢复初始状态

*/

public void onFooterRefreshComplete() {

setHeaderTopMargin(-mHeaderViewHeight);

mFooterImageView.setVisibility(View.VISIBLE);

mFooterImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow_up);

mFooterTextView.setText(R.string.pull_to_refresh_footer_pull_label);

mFooterProgressBar.setVisibility(View.GONE);

// mHeaderUpdateTextView.setText("");

mFooterState = PULL_TO_REFRESH;

}

/**

* Set a text to represent when the list was last updated.

*

* @param lastUpdated

* Last updated at.

*/

public void setLastUpdated(CharSequence lastUpdated) {

if (lastUpdated != null) {

mHeaderUpdateTextView.setVisibility(View.VISIBLE);

mHeaderUpdateTextView.setText(lastUpdated);

} else {

mHeaderUpdateTextView.setVisibility(View.GONE);

}

}

/**

* 获取当前header view 的topMargin

*

*/

private int getHeaderTopMargin() {

LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();

return params.topMargin;

}

// /**

// * lock

// *

// */

// private void lock() {

// mLock = true;

// }

//

// /**

// * unlock

// *

// */

// private void unlock() {

// mLock = false;

// }

/**

* set headerRefreshListener

*

* @param headerRefreshListener

*/

public void setOnHeaderRefreshListener(

OnHeaderRefreshListener headerRefreshListener) {

mOnHeaderRefreshListener = headerRefreshListener;

}

public void setOnFooterRefreshListener(

OnFooterRefreshListener footerRefreshListener) {

mOnFooterRefreshListener = footerRefreshListener;

}

/**

* Interface definition for a callback to be invoked when list/grid footer

* view should be refreshed.

*/

public interface OnFooterRefreshListener {

public void onFooterRefresh(PullToRefreshView view);

}

/**

* Interface definition for a callback to be invoked when list/grid header

* view should be refreshed.

*/

public interface OnHeaderRefreshListener {

public void onHeaderRefresh(PullToRefreshView view);

}

}

然后开始布局我们的ExpandableListView来实现滚动的时候title永远在上方。具体代码如下

@Override

public void onScroll(AbsListView view, int firstVisibleItem,

int visibleItemCount, int totalItemCount) {

final ExpandableListView listView = (ExpandableListView) view;

/**

* calculate point (0,0)

*/

int npos = view.pointToPosition(0, 0);// 其实就是firstVisibleItem

if (npos == AdapterView.INVALID_POSITION)// 如果第一个位置值无效

return;

long pos = listView.getExpandableListPosition(npos);

int childPos = ExpandableListView.getPackedPositionChild(pos);// 获取第一行child的id

int groupPos = ExpandableListView.getPackedPositionGroup(pos);// 获取第一行group的id

if (childPos == AdapterView.INVALID_POSITION) {// 第一行不是显示child,就是group,此时没必要显示指示器

View groupView = listView.getChildAt(npos

- listView.getFirstVisiblePosition());// 第一行的view

indicatorGroupHeight = groupView.getHeight();// 获取group的高度

indicatorGroup.setVisibility(View.GONE);// 隐藏指示器

} else {

indicatorGroup.setVisibility(View.VISIBLE);// 滚动到第一行是child,就显示指示器

}

// get an error data, so return now

if (indicatorGroupHeight == 0) {

return;

}

// update the data of indicator group view

if (groupPos != indicatorGroupId) {// 如果指示器显示的不是当前group

mAdapter.getGroupView(groupPos, listView.isGroupExpanded(groupPos),

indicatorGroup.getChildAt(0), null);// 将指示器更新为当前group

indicatorGroupId = groupPos;

Log.e(TAG, PRE + "bind to new group,group position = " + groupPos);

// mAdapter.hideGroup(indicatorGroupId); // we set this group view

// to be hided

// 为此指示器增加点击事件

indicatorGroup.setOnClickListener(new OnClickListener() {

public void onClick(View v) {

// TODO Auto-generated method stub

listView.collapseGroup(indicatorGroupId);

}

});

}

if (indicatorGroupId == -1) // 如果此时grop的id无效,则返回

return;

/**

* calculate point (0,indicatorGroupHeight) 下面是形成往上推出的效果

*/

int showHeight = indicatorGroupHeight;

int nEndPos = listView.pointToPosition(0, indicatorGroupHeight);// 第二个item的位置

if (nEndPos == AdapterView.INVALID_POSITION)// 如果无效直接返回

return;

long pos2 = listView.getExpandableListPosition(nEndPos);

int groupPos2 = ExpandableListView.getPackedPositionGroup(pos2);// 获取第二个group的id

if (groupPos2 != indicatorGroupId) {// 如果不等于指示器当前的group

View viewNext = listView.getChildAt(nEndPos

- listView.getFirstVisiblePosition());

showHeight = viewNext.getTop();

Log.e(TAG, PRE + "update the show part height of indicator group:"

+ showHeight);

}

// update group position

MarginLayoutParams layoutParams = (MarginLayoutParams) indicatorGroup

.getLayoutParams();

layoutParams.topMargin = -(indicatorGroupHeight - showHeight);

indicatorGroup.setLayoutParams(layoutParams);

if (visibleItemCount + firstVisibleItem == totalItemCount) {

Log.e("log", "滑到底部");

}

}

项目下载地址:http://download.csdn.net/detail/a8583850/8668463
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐