偷懒了一天,去给人装系统去了~ 今天继续更 自己敲代码实现下拉刷新上滑加载更多

"你会装系统吗?""会""我电脑打不开了""好我马上到"....然后就是从WIN7开始装 装完直接装WIN10 然后装软件...然后..........妹子为什么不能自己学学装系统呢......

 

下拉刷新的原理很简单,下拉刷新也是listview的头布局 只不过靠setPadding(0,-PaddingTop,0,0),让他隐藏在头部局上面(这么说只是为了更形象,其实是头部的一条缝)  

 

下拉刷新的原理就是这样. 下面来敲代码 自定义我们的refreshListView

第一步  先实现我们的下拉刷新这几个字 

网上下的后台资源 JSON数据有点旧 不过这不是重点,第一步 我们给listview添加下拉刷新的头布局, 很简单就不多说.

<?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:orientation="horizontal" >

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="10dp"
        android:paddingLeft="10dp"
        android:paddingTop="10dp" >

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@drawable/common_listview_headview_red_arrow" />

        <ProgressBar
            android:id="@+id/pb_loading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:visibility="invisible"
            android:indeterminateDrawable="@drawable/custom_progress" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉刷新"
            android:textColor="#f00"
            android:textSize="17sp" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:text="2016-02-16 22:01"
            android:textColor="#9e9e9e"
            android:textSize="15sp" />
    </LinearLayout>

</LinearLayout>

自带progressbar的太丑了简单自定义了一个 旋转动画和渐变效果  我设置

android:visibility="invisible"

 刷新ing的时候再设置可见.

custom_progress

 

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360" >

    <shape
        android:innerRadius="15dp"
        android:shape="ring"
        android:thickness="3dp"
        android:useLevel="false" >
        <gradient
            android:centerColor="#5f00"
            android:endColor="#fff"
            android:startColor="#f00"
            android:type="sweep" />
    </shape>

</rotate>

2 这一步我们要他隐藏:

那么 我们在我们的RefreshListView中先拿到头布局的高度 并把它隐藏:

/**
* 初始化头布局
*/
private void initHeaderView() {
mHeaderView = View.inflate(getContext(), R.layout.list_refrsh_header,
        null);
this.addHeaderView(mHeaderView);// 添加头布局

// 隐藏头布局(1, 获取头布局高度, 2.设置负paddingTop,布局就会往上走)
// int height = mHeaderView.getHeight();//此处无法获取高度,因为布局还没有绘制完成
// 绘制之前就要获取布局高度
mHeaderView.measure(0, 0);// 手动测量布局
mHeaderViewHeight = mHeaderView.getMeasuredHeight();// 测量之后的高度
// 隐藏头布局
mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);

tvTitle = (TextView) mHeaderView.findViewById(R.id.tv_title);
ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arrow);
pbLoading = (ProgressBar) mHeaderView.findViewById(R.id.pb_loading);
tvTime = (TextView) mHeaderView.findViewById(R.id.tv_time);


}
View Code

3 这样就看不到了. 那么下部我们应该把它拉出来, 通过获得手指在Y轴上的移动距离dy , 当dy-mHeaderViewHeight > 0  且是第一个可见条目的id == 0 那么 头布局可见

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        startY = (int) ev.getY();
        break;
    case MotionEvent.ACTION_MOVE:
        if (startY == -1) {// 如果用户按住头条新闻向下滑动, 会导致listview无法拿到ACTION_DOWN,
                            // 此时要重新获取startY
            startY = (int) ev.getY();
        }

        // 如果当前正在刷新, 什么都不做了
        if (mCurrentState == STATE_REFRESHING) {
            break;
        }

        int endY = (int) ev.getY();
        int dy = endY - startY;

        if (dy > 0 && getFirstVisiblePosition() == 0) {// 向下滑动&当前显示的是第一个item,才允许下拉刷新
            int paddingTop = dy - mHeaderViewHeight;// 计算当前的paddingtop值

            // 根据padding切换状态
            if (paddingTop >= 0
                    && mCurrentState != STATE_RELEASE_TO_REFRESH) {
                // 切换到松开刷新
                mCurrentState = STATE_RELEASE_TO_REFRESH;
                refreshState();
            } else if (paddingTop < 0
                    && mCurrentState != STATE_PULL_TO_REFRESH) {
                // 切换到下拉刷新
                mCurrentState = STATE_PULL_TO_REFRESH;
                refreshState();
            }

            mHeaderView.setPadding(0, paddingTop, 0, 0);// 重新设置头布局padding
            return true;
        }

        break;
    case MotionEvent.ACTION_UP:
        startY = -1;// 起始坐标归零

        if (mCurrentState == STATE_RELEASE_TO_REFRESH) {
            // 如果当前是松开刷新, 就要切换为正在刷新
            mCurrentState = STATE_REFRESHING;
            // 显示头布局
            mHeaderView.setPadding(0, 0, 0, 0);
            
            refreshState();

//            // 下拉刷新回调
//            if (mListener != null) {
//                mListener.onRefresh();
//            }

        } else if (mCurrentState == STATE_PULL_TO_REFRESH) {
            // 隐藏头布局
            mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
        }

        break;

    default:
        break;
    }

    return super.onTouchEvent(ev);
}

/**
 * 初始化箭头动画
 */
private void initAnim() {
    animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    animUp.setDuration(500);
    animUp.setFillAfter(true);// 保持状态

    animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF,
            0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    animDown.setDuration(500);
    animDown.setFillAfter(true);// 保持状态
}


/**
 * 根据当前状态刷新界面
 */
private void refreshState() {
    switch (mCurrentState) {
    case STATE_PULL_TO_REFRESH:
        tvTitle.setText("下拉刷新");
        // 箭头向下移动
        ivArrow.startAnimation(animDown);
        // 隐藏进度条
        pbLoading.setVisibility(View.INVISIBLE);
        ivArrow.setVisibility(View.VISIBLE);
        break;
    case STATE_RELEASE_TO_REFRESH:
        tvTitle.setText("松开刷新");
        // 箭头向上移动
        ivArrow.startAnimation(animUp);
        // 隐藏进度条
        pbLoading.setVisibility(View.INVISIBLE);
        ivArrow.setVisibility(View.VISIBLE);
        break;
    case STATE_REFRESHING:
        tvTitle.setText("正在刷新...");
        pbLoading.setVisibility(View.VISIBLE);
        ivArrow.clearAnimation();// 必须清除动画,才能隐藏控件
        ivArrow.setVisibility(View.INVISIBLE);
        break;

    default:
        break;
    }
}

4 我们暂时完成了下拉刷新 是不是有点激动! 原理居然这么简单!说白了就是个paddingTop再加入状态判断和设置.

但是我们现在的下拉刷新不会回去. 那么我们来实现它回去的逻辑 ,下拉刷新接口回调刷新数据后会返回成功或者失败 ,那么 我们创建一个方法

onRefreshComplete

来通知listview 是成功还是失败然后做相应的UI操作

/**
     * 从服务器获取数据
     */
    private void getDataFromSevice() {
        HttpUtils httpUtils = new HttpUtils();

        httpUtils.send(HttpMethod.GET, mUrl, new RequestCallBack<String>() {

            @Override
            public void onSuccess(ResponseInfo<String> responseInfo) {
                // 请求成功调用
                String result = responseInfo.result;// 获得json字符串

                processResult(result);
                CacheUtils.setCache(mUrl, result, mActivity);
                mListView.onRefreshComplete(true);
            }

            @Override
            public void onFailure(HttpException error, String msg) {
                // 请求失败调用
                error.printStackTrace();
                Toast.makeText(mActivity, msg, Toast.LENGTH_LONG).show();
                mListView.onRefreshComplete(false);
            }
        });
    }

RefreshListView: 不管成功还是失败都隐藏掉

// 刷新完成
public void onRefreshComplete(boolean success) {
    if (success) {
        // 隐藏头布局
        mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
        mCurrentState = STATE_PULL_TO_REFRESH;
        // 隐藏进度条
        pbLoading.setVisibility(View.INVISIBLE);
        ivArrow.setVisibility(View.VISIBLE);
        tvTitle.setText("下拉刷新");
            setCurrentTime();
    
    }else{
        // 隐藏头布局
        mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
        mCurrentState = STATE_PULL_TO_REFRESH;
        // 隐藏进度条
        pbLoading.setVisibility(View.INVISIBLE);
        ivArrow.setVisibility(View.VISIBLE);
        tvTitle.setText("下拉刷新");
        Toast.makeText(getContext(), "刷新失败请检查网络连接", Toast.LENGTH_LONG).show();
    }
}

现在是这样 我把tomcat服务器关了:

5 继续. 上滑加载更多: 原理 添加listview脚布局 然后通过设置-paddingTop隐藏掉

跟下拉过程一样 只不过回调的链接不一样 上拉加载更多的链接一般是more : "/10007/list_2.json"这样的     需要重新解析more的json数据

首先 新建一个脚布局:

很简单 一个ProgressBar 和一个TextView

<?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:gravity="center"
    android:orientation="horizontal" >

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:indeterminateDrawable="@drawable/custom_progress" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:text="加载中..."
        android:textColor="#f00"
        android:textSize="17sp" />

</LinearLayout>

 设置给我们的 RefreshListView

/**
 * 初始化脚布局
 */
private void initFooterView() {
    mFooterView = View.inflate(getContext(), R.layout.list_refresh_footer,
            null);
    this.addFooterView(mFooterView);

    mFooterView.measure(0, 0);
    mFooterViewHeight = mFooterView.getMeasuredHeight();

   
}

好 大概就这样了.

6 隐藏它 并设置监听 当listview是可见的最后一个条目的时候显示并加载更多

/**
 * 初始化脚布局
 */
private void initFooterView() {
    mFooterView = View.inflate(getContext(), R.layout.list_refresh_footer,
            null);
    this.addFooterView(mFooterView);

    mFooterView.measure(0, 0);
    mFooterViewHeight = mFooterView.getMeasuredHeight();

    // 隐藏脚布局
    mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);

    // 设置滑动监听
    this.setOnScrollListener(this);
}

监听是否是listview可见的最后一个条目,然后通过接口的回调 来实现加载更多的方法

声明一个全局变量 private boolean isLoadingMore;// 来标记是否正在加载更多

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == SCROLL_STATE_IDLE) {
            int lastVisiblePosition = getLastVisiblePosition();// 当前界面显示的最后一个item的位置
            if (lastVisiblePosition >= getCount() - 1 && !isLoadingMore) {
                isLoadingMore = true;
                // System.out.println("到底了");
                // 加载更多了....(到底了)
                // 显示脚布局
                mFooterView.setPadding(0, 0, 0, 0);
                // listview设置当前要展示的item的位置
                setSelection(getCount() - 1);// 跳到加载更多item的位置去展示

                if (mListener != null) {
                    mListener.loadMore();
                }
            }
        }
    }

 7 然后再新闻界面页签页面  重写接口的 loadMore方法 并判断more的Url是否为空

        // 设置下拉刷新监听
        lvList.setOnRefreshListener(new OnRefreshListener() {

            @Override
            public void onRefresh() {
                // 从网络加载数据
                getDataFromServer();
            }

            @Override
            public void loadMore() {
                // 加载更多数据
                if (mMoreUrl != null) {
                    System.out.println("加载下一页数据...");
                    getMoreDataFromServer();
                } else {
                    lvList.onRefreshComplete(true);// 收起加载更多布局
                    Toast.makeText(mActivity, "没有更多数据了", Toast.LENGTH_SHORT)
                            .show();
                }
            }
        });

8 新建 getMoreDataFromServer();  来请求more的数据

/**
     * 加载更多数据
     */
    protected void getMoreDataFromServer() {
        HttpUtils utils = new HttpUtils();
        utils.send(HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() {

            @Override
            public void onSuccess(ResponseInfo<String> responseInfo) {
                String result = responseInfo.result;
                processResult(result, true);
                // 收起加载更多布局
                mListView.onRefreshComplete(true);
            }

            @Override
            public void onFailure(HttpException error, String msg) {
                error.printStackTrace();
                Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show();
                // 收起加载更多布局
                mListView.onRefreshComplete(false);
            }
        });
    }


9 然后新建一个boolean 用来判断是是否是ismore 不是的话就是普通的加载

    protected void processResult(String result, Boolean isMore) {

        Gson gson = new Gson();
        NewsDatas mNewsDatas = gson.fromJson(result, NewsDatas.class);
        if (!TextUtils.isEmpty(mNewsDatas.data.more) ) {
            // 初始化地址
            mMoreUrl = Contants.SERVER_URL + mNewsDatas.data.more;
        } else {
            // 没有下一页了
            mMoreUrl = null;
        }
        
        if (!isMore) {
            // 获取头条新闻数据
            mTopNewsList = mNewsDatas.data.topnews;

            if (mTopNewsList != null) {
                mTopNewsAdapter = new TopNewsAdapter();

                mViewPager.setAdapter(mTopNewsAdapter);
                // 给页面指示器设置Viewpager
                mCirclePageIndicator.setViewPager(mViewPager);//将指示器和viewpager绑定
                mCirclePageIndicator.setSnap(true);
                // 给mCirclePageIndicator 设置监听
                mCirclePageIndicator.setOnPageChangeListener(new OnPageChangeListener() {

                    @Override
                    public void onPageSelected(int position) {
                        // TODO Auto-generated method stub
                        String title = mTopNewsList.get(position).title;
                        topNewsTitle.setText(title);
                    }

                    @Override
                    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void onPageScrollStateChanged(int state) {
                        // TODO Auto-generated method stub

                    }
                });

                mCirclePageIndicator.onPageSelected(0);// 将小圆点位置归零,
                                                        // 解决它会在页面销毁时仍记录上次位置的bug
                topNewsTitle.setText(mTopNewsList.get(0).title);// 初始化第一页标题
            }

            // 初始化新闻列表
            newsListViewData = mNewsDatas.data.news;
//            System.out.println("新闻列表数据:" + newsListViewData);
            if (newsListViewData != null) {
                newsListViewDataAdapter = new MyListViewAdapter();
                mListView.setAdapter(newsListViewDataAdapter);
            }
        }
        else {
            // 加载更多
                        ArrayList<News> moreData = mNewsDatas.data.news;
                        newsListViewData.addAll(moreData);// 追加数据
                        newsListViewDataAdapter.notifyDataSetChanged();// 刷新listview
        }

    }

10 重写刷新完成的方法 区分隐藏头布局还是 脚布局
    // 刷新完成
    public void onRefreshComplete(boolean success) {
        if (!isLoadingMore) {
            // 隐藏头布局
            mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);
            mCurrentState = STATE_PULL_TO_REFRESH;
            // 隐藏进度条
            pbLoading.setVisibility(View.INVISIBLE);
            ivArrow.setVisibility(View.VISIBLE);
            tvTitle.setText("下拉刷新");
            // 刷新失败,不需要更新时间
            if (success) {
                setCurrentTime();
            }
        } else {
            // 隐藏脚布局
            mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
            isLoadingMore = false;
        }
    }

最终效果:  标题带2的是刷新出来的

项目地址

https://github.com/AceInAndroid/AnYangNews

 
原文地址:https://www.cnblogs.com/AceIsSunshineRain/p/5193838.html