仿今日头条下拉出现SearchBar,再下拉刷新效果,SearchListView实现以及原理讲解

来源:互联网 发布:C语言exit(-1) 编辑:程序博客网 时间:2024/06/05 20:19

先看效果

分别是我的效果和今日头条的效果: 
这里写图片描述这里写图片描述

以上效果包括: 
1.如果下拉的高度超过search view的高度的3/4,但是小于head view高度,则松开手时search view自动出现 
2.如果下拉的高度小于search view的高度的1/4,则松开手时search view自动回弹消失 
3.如果下拉的高度超过head view的总高度,则松手进行刷新 
4.刷新完成自动隐藏search view

实现原理讲解

参考了 github开源项目:[https://github.com/vivian8725118/SearchListView ] 但是这个开源调用listview.setOnItemClickListener的时候,下拉出现search view,下拉刷新都响应了onItemClick,这显然是不对的,我在onTouchEvent的Action_Up中进行了修改,以保证能正确响应点击事件。 
提示: 这个效果是基于PullToRefreshListView实现的,如果对下拉刷新listview实现不明白的,可以先看这篇博客[http://blog.csdn.net/u010335298/article/details/51098755] 
原理讲解 
这里写图片描述 
如图,将搜索部分的view放进ListView的头view中,在触摸事件中处理search view 的显示和隐藏。

1.我给listview定义了五种状态

    public static final int STATE_NONE  = 0;    public static final int STATE_PULL_TO_SHOW_SEARCH_VIEW  = 1; // 下拉去展示search_view    public static final int STATE_PULL_TO_REFRESH = 2; //下拉去刷新    public static final int STATE_RELEASE_TO_REFRESH = 3; // 释放进行刷新    public static final int STATE_REFRESHING = 4; // 正在刷新

稍微讲解以下这几种状态: 
STATE_PULL_TO_SHOW_SEARCH_VIEW 指的是search view出现了一部分但是没有完全出现的时候 
STATE_PULL_TO_REFRESH 指的是search view完全出现,但是head view没有完全出现的时候 
STATE_RELEASE_TO_REFRESH 指的是head view完全出现的时候

2.添加search view

 private void initHeaderView() {        headerView =  View.inflate(getContext(), R.layout.search_header_listview, null);        searchContainer = (RelativeLayout) headerView.findViewById(R.id.search_container);        headerTv = (TextView) headerView.findViewById(R.id.tvHead);        headerView.measure(0, 0); // 系统会帮我们测量出headerView的高度        headerHeight = refreshHeight = headerView.getMeasuredHeight();        Log.d("zyr", "--------------------headerHeight :" + headerHeight);        headerView.setPadding(0, -headerHeight, 0, 0);        invalidate();        Log.d("zyr", "----------------------headerPaddingTop :" + headerView.getPaddingTop());        super.addHeaderView(headerView, null, false);    }    private void addSearchView() {        searchView = LayoutInflater.from(getContext()).inflate(R.layout.search_view, null);        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);        searchContainer.addView(searchView , layoutParams);        searchContainer.measure(0, 0);        searchContainerHeight = searchContainer.getMeasuredHeight();        Log.d("zyr", "--------------------searchContainerHeight :" + searchContainerHeight);        headerHeight = searchContainerHeight + headerHeight ;        headerView.setPadding(0, - headerHeight, 0, 0);        invalidate();        Log.d("zyr", "--------------------headerHeight :" + headerHeight);        Log.d("zyr", "----------------------headerPaddingTop :" + headerView.getPaddingTop());    }

3.触摸事件的处理:我详细的写了注释

/***************************    Touch***************************************/    @Override    public boolean onTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN :                downY = lastMoveY = (int) ev.getY();                downX = (int) ev.getX();                break;            case MotionEvent.ACTION_MOVE :                moveY = (int) ev.getY();                moveDiff = (moveY - lastMoveY)/2 ;                lastMoveY = moveY ;                diff = moveY - downY;                //计算移动后的paddingTop                int paddingTop =   headerView.getPaddingTop() + moveDiff ;                // 如果: 第一个可见                if(getFirstVisiblePosition() == 0 && Math.abs(diff) > 50){                    switch (state){                        case STATE_NONE :                        //当状态是none的时候,向下拉才做处理                            if(diff > 0){                                state = STATE_PULL_TO_SHOW_SEARCH_VIEW;                                headerView.setPadding(0, paddingTop, 0, 0);                                Log.d("zyr", "--------------paddingTop:" + paddingTop);                                return true;                            }                            break;                        case STATE_PULL_TO_SHOW_SEARCH_VIEW:                        case STATE_PULL_TO_REFRESH:                        case STATE_RELEASE_TO_REFRESH:                            if (paddingTop >= 0 ) { // 完全显示了.                                Log.i("zyr", "ACTION_MOVE 松开刷新");                                state = STATE_RELEASE_TO_REFRESH;                                refreshHeaderView();                            } else if (paddingTop < 0 && Math.abs(paddingTop) <= refreshHeight) {//search view完全显示了,head view没有完全显示                                Log.i("zyr", "ACTION_MOVE 下拉刷新");                                state = STATE_PULL_TO_REFRESH;                                refreshHeaderView();                            }  else if(paddingTop < 0 && Math.abs(paddingTop) > refreshHeight) {//search view没有完全显示                                Log.i("zyr", "ACTION_MOVE 下拉显示Search View");                                state = STATE_PULL_TO_SHOW_SEARCH_VIEW;                                refreshHeaderView();                            }  else if ( paddingTop == 0){                                state = STATE_NONE;                                break;                            }                            // 下拉头布局                            Log.d("zyr","--------------paddingTop:" + paddingTop);                            headerView.setPadding(0, paddingTop, 0, 0);                            return true;                        default:                            break;                    }                }                break;            case MotionEvent.ACTION_UP :                upY = (int)ev.getY();                upX = (int)ev.getX();                // 判读是不是点击事件                if(Math.abs(upY - downY) < 50 && Math.abs(upX - downX) < 50){                    return super.onTouchEvent(ev);                }                int headPaddingTop = headerView.getPaddingTop();                Log.d("zyr","--------------MotionEvent.ACTION_UP paddingTop:" + headPaddingTop);                Log.d("zyr","--------------MotionEvent.ACTION_UP state:" + state);                // 判断当前的状态                switch (state){                    case STATE_PULL_TO_SHOW_SEARCH_VIEW:                        if ( headerHeight - Math.abs(headPaddingTop) <= searchContainerHeight * 0.25){                            Log.i("zyr", "下拉高度小于Search View * 0.25,隐藏search view");                            // 隐藏头布局                            mScroller.startScroll(0,headPaddingTop,0,- headerHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_NONE;                        } else if( headerHeight - Math.abs(headPaddingTop) >= searchContainerHeight * 0.75) {                            Log.i("zyr", "下拉高度大于Search View * 0.75,显示search view");                            // 显示 search view                            mScroller.startScroll(0,headPaddingTop,0,- refreshHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_PULL_TO_REFRESH;                        } else if ( moveDiff > 0){                            Log.i("zyr", "下拉高度在Search View * 0.25 - 0.75,下拉,显示search view");                            // 显示 search view                            mScroller.startScroll(0,headPaddingTop,0,- refreshHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_PULL_TO_REFRESH;                        } else {                            Log.i("zyr", "下拉高度在Search View * 0.25 - 0.75,上滑,隐藏search view");                            // 隐藏头布局                            mScroller.startScroll(0,headPaddingTop,0,- headerHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_NONE;                        }                        return true;                    case STATE_PULL_TO_REFRESH:                        Log.i("zyr", "ACTION_UP 下拉刷新数据");                        // 显示search view                        mScroller.startScroll(0,headerView.getPaddingTop(),0,- refreshHeight - headerView.getPaddingTop(),DURATION);                        postInvalidate();                        return true;                    case STATE_RELEASE_TO_REFRESH:                        Log.i("zyr", "ACTION_UP 释放刷新数据");                        // 把头布局设置为完全显示状态                        mScroller.startScroll(0,headerView.getPaddingTop(),0,-headerView.getPaddingTop(),DURATION);                        postInvalidate();                        // 进入到正在刷新中状态                        state = STATE_REFRESHING;                        refreshHeaderView();                        if (mOnRefreshListener != null) {                            mOnRefreshListener.onDownPullRefresh(); // 调用使用者的监听方法                        }                        return true;                }                break;            default :                break;        }        return super.onTouchEvent(ev);    }

详细代码

CustomSearchListView

package com.example.myapp.view;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.animation.DecelerateInterpolator;import android.widget.AbsListView;import android.widget.ListView;import android.widget.RelativeLayout;import android.widget.Scroller;import android.widget.TextView;import com.example.myapp.R;/** * Created by zyr * DATE: 16-4-8 * Time: 下午7:09 * Email: yanru.zhang@renren-inc.com * * 通过listview addHeaderView来实现下拉刷新 */public class CustomSearchListView extends ListView implements AbsListView.OnScrollListener {    private Context mContext;    private View headerView;    private TextView headerTv;    private View searchView;    private RelativeLayout searchContainer;    private int headerHeight ;    private int refreshHeight ;    private int searchContainerHeight;    private Scroller mScroller;    public static final int STATE_NONE  = 0;    public static final int STATE_PULL_TO_SHOW_SEARCH_VIEW  = 1; // 下拉去展示search_view    public static final int STATE_PULL_TO_REFRESH = 2; //下拉去刷新    public static final int STATE_RELEASE_TO_REFRESH = 3; // 释放进行刷新    public static final int STATE_REFRESHING = 4; // 正在刷新    private int state = STATE_NONE;    private int downX , downY , moveY , lastMoveY , upY , upX;    private int diff , moveDiff;    public static final int DURATION = 500;    private OnRefreshListener mOnRefreshListener;    public interface OnRefreshListener{        void onDownPullRefresh();    }    /************************   构造****************************************/    public CustomSearchListView(Context context) {        this(context, null);    }    public CustomSearchListView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public CustomSearchListView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        mContext = context;        initScroller();        initHeaderView();        addSearchView();        setOnScrollListener(this);    }    private void initScroller() {        mScroller = new Scroller(getContext(), new DecelerateInterpolator());    }    @Override    public void computeScroll() {        super.computeScroll();        if(mScroller.computeScrollOffset()){            Log.d("zyr" , "-----------computeScroll mScroller.getCurrY():" + mScroller.getCurrY());            headerView.setPadding(0, mScroller.getCurrY(), 0, 0);        }    }    private void initHeaderView() {        headerView =  View.inflate(getContext(), R.layout.search_header_listview, null);        searchContainer = (RelativeLayout) headerView.findViewById(R.id.search_container);        headerTv = (TextView) headerView.findViewById(R.id.tvHead);        headerView.measure(0, 0); // 系统会帮我们测量出headerView的高度        headerHeight = refreshHeight = headerView.getMeasuredHeight();        Log.d("zyr", "--------------------headerHeight :" + headerHeight);        headerView.setPadding(0, -headerHeight, 0, 0);        invalidate();        Log.d("zyr", "----------------------headerPaddingTop :" + headerView.getPaddingTop());        super.addHeaderView(headerView, null, false);    }    private void addSearchView() {        searchView = LayoutInflater.from(getContext()).inflate(R.layout.search_view, null);        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);        searchContainer.addView(searchView , layoutParams);        searchContainer.measure(0, 0);        searchContainerHeight = searchContainer.getMeasuredHeight();        Log.d("zyr", "--------------------searchContainerHeight :" + searchContainerHeight);        headerHeight = searchContainerHeight + headerHeight ;        headerView.setPadding(0, - headerHeight, 0, 0);        invalidate();        Log.d("zyr", "--------------------headerHeight :" + headerHeight);        Log.d("zyr", "----------------------headerPaddingTop :" + headerView.getPaddingTop());    }    /**************************     Scroll******************************/    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {    }    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {    }    /***************************    Touch***************************************/    @Override    public boolean onTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN :                downY = lastMoveY = (int) ev.getY();                downX = (int) ev.getX();                break;            case MotionEvent.ACTION_MOVE :                moveY = (int) ev.getY();                moveDiff = (moveY - lastMoveY)/2 ;                lastMoveY = moveY ;                diff = moveY - downY;                //                int paddingTop =   headerView.getPaddingTop() + moveDiff ;                // 如果: 第一个可见                if(getFirstVisiblePosition() == 0 && Math.abs(diff) > 50){                    switch (state){                        case STATE_NONE :                            if(diff > 0){                                state = STATE_PULL_TO_SHOW_SEARCH_VIEW;                                headerView.setPadding(0, paddingTop, 0, 0);                                Log.d("zyr", "--------------paddingTop:" + paddingTop);                                return true;                            }                            break;                        case STATE_PULL_TO_SHOW_SEARCH_VIEW:                        case STATE_PULL_TO_REFRESH:                        case STATE_RELEASE_TO_REFRESH:                            if (paddingTop >= 0 ) { // 完全显示了.                                Log.i("zyr", "ACTION_MOVE 松开刷新");                                state = STATE_RELEASE_TO_REFRESH;                                refreshHeaderView();                            } else if (paddingTop < 0 && Math.abs(paddingTop) <= refreshHeight) {                                Log.i("zyr", "ACTION_MOVE 下拉刷新");                                state = STATE_PULL_TO_REFRESH;                                refreshHeaderView();                            }  else if(paddingTop < 0 && Math.abs(paddingTop) > refreshHeight) {                                Log.i("zyr", "ACTION_MOVE 下拉显示Search View");                                state = STATE_PULL_TO_SHOW_SEARCH_VIEW;                                refreshHeaderView();                            }  else if ( paddingTop == 0){                                state = STATE_NONE;                                break;                            }                            // 下拉头布局                            Log.d("zyr","--------------paddingTop:" + paddingTop);                            headerView.setPadding(0, paddingTop, 0, 0);                            return true;                        default:                            break;                    }                }                break;            case MotionEvent.ACTION_UP :                upY = (int)ev.getY();                upX = (int)ev.getX();                // 判读是不是点击事件                if(Math.abs(upY - downY) < 50 && Math.abs(upX - downX) < 50){                    return super.onTouchEvent(ev);                }                // 判断当前的状态                int headPaddingTop = headerView.getPaddingTop();                Log.d("zyr","--------------MotionEvent.ACTION_UP paddingTop:" + headPaddingTop);                Log.d("zyr","--------------MotionEvent.ACTION_UP state:" + state);                switch (state){                    case STATE_PULL_TO_SHOW_SEARCH_VIEW:                        if ( headerHeight - Math.abs(headPaddingTop) <= searchContainerHeight * 0.25){                            Log.i("zyr", "下拉高度小于Search View * 0.25,隐藏search view");                            // 隐藏头布局                            mScroller.startScroll(0,headPaddingTop,0,- headerHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_NONE;                        } else if( headerHeight - Math.abs(headPaddingTop) >= searchContainerHeight * 0.75) {                            Log.i("zyr", "下拉高度大于Search View * 0.75,显示search view");                            // 显示 search view                            mScroller.startScroll(0,headPaddingTop,0,- refreshHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_PULL_TO_REFRESH;                        } else if ( moveDiff > 0){                            Log.i("zyr", "下拉高度在Search View * 0.25 - 0.75,下拉,显示search view");                            // 显示 search view                            mScroller.startScroll(0,headPaddingTop,0,- refreshHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_PULL_TO_REFRESH;                        } else {                            Log.i("zyr", "下拉高度在Search View * 0.25 - 0.75,上滑,隐藏search view");                            // 隐藏头布局                            mScroller.startScroll(0,headPaddingTop,0,- headerHeight - headPaddingTop , DURATION);                            postInvalidate();                            state = STATE_NONE;                        }                        return true;                    case STATE_PULL_TO_REFRESH:                        Log.i("zyr", "ACTION_UP 下拉刷新数据");                        // 显示search view                        mScroller.startScroll(0,headerView.getPaddingTop(),0,- refreshHeight - headerView.getPaddingTop(),DURATION);                        postInvalidate();                        return true;                    case STATE_RELEASE_TO_REFRESH:                        Log.i("zyr", "ACTION_UP 释放刷新数据");                        // 把头布局设置为完全显示状态                        mScroller.startScroll(0,headerView.getPaddingTop(),0,-headerView.getPaddingTop(),DURATION);                        postInvalidate();                        // 进入到正在刷新中状态                        state = STATE_REFRESHING;                        refreshHeaderView();                        if (mOnRefreshListener != null) {                            mOnRefreshListener.onDownPullRefresh(); // 调用使用者的监听方法                        }                        return true;                }                break;            default :                break;        }        return super.onTouchEvent(ev);    }    /**     * 根据currentState刷新头布局的状态     */    private void refreshHeaderView() {        switch (state) {            case STATE_PULL_TO_REFRESH : // 下拉刷新状态            case STATE_PULL_TO_SHOW_SEARCH_VIEW:                headerTv.setText("下拉刷新");                break;            case STATE_RELEASE_TO_REFRESH : // 松开刷新状态                headerTv.setText("松开刷新");                break;            case STATE_REFRESHING : // 正在刷新中状态                headerTv.setText("正在刷新中...");                break;            default :                break;        }    }    /**     * 隐藏头布局     */    public void hideHeaderView() {        post(new Runnable() {            @Override            public void run() {                mScroller.startScroll(0,headerView.getPaddingTop(),0,- headerHeight -headerView.getPaddingTop(),DURATION);                postInvalidate();                headerTv.setText("下拉刷新");                state = STATE_NONE;            }        });    }    /**     * 设置刷新监听事件     *     * @param listener     */    public void setOnRefreshListener(OnRefreshListener listener) {        mOnRefreshListener = listener;    }}

search_header_listview.xml

<?xml version="1.0" encoding="utf-8"?><!-- ListView的头部 --><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:orientation="vertical">    <!-- 内容 -->    <RelativeLayout        android:id="@+id/head_contentLayout"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:gravity="center">        <RelativeLayout android:id="@+id/refresh_container"            android:layout_width="fill_parent"            android:layout_height="50dp"            android:gravity="center">            <TextView                android:id="@+id/tvHead"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="@string/pull_to_refresh_pull_label"                android:textColor="@color/black"                android:textSize="15.5sp"                android:textStyle="bold" />        </RelativeLayout>        <RelativeLayout android:id="@+id/search_container"            android:layout_below="@+id/refresh_container"            android:layout_width="match_parent"            android:layout_height="wrap_content">        </RelativeLayout>    </RelativeLayout></LinearLayout>

search_view.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView        android:layout_width="match_parent"        android:layout_height="30dp"        android:text="Search"        android:gravity="center"        android:background="@drawable/common_bg2"        android:layout_margin="5dp"        android:textSize="15sp"/></LinearLayout>

CustomSearchListViewTestActivity

package com.example.myapp.activity;import android.view.View;import android.widget.AdapterView;import android.widget.FrameLayout;import com.example.myapp.R;import com.example.myapp.adapter.CommonAdapter;import com.example.myapp.util.Methods;import com.example.myapp.view.CustomPullToRefreshListView2;import com.example.myapp.view.CustomSearchListView;import java.util.ArrayList;/** * Created by zyr * DATE: 16-4-6 * Time: 下午4:05 * Email: yanru.zhang@renren-inc.com */public class CustomSearchListViewTestActivity extends BaseActivity {    private FrameLayout headRoot;    private FrameLayout headContainer;    private CustomSearchListView listView;//    private PullToRefreshListView listView;    private CommonAdapter commonAdapter;    private ArrayList<String> strings  = new ArrayList<>();    @Override    protected void initView() {        listView = (CustomSearchListView) findViewById(R.id.listview);        for(int i=0;i<20;i++){            strings.add("zyr" + i);        }        commonAdapter = new CommonAdapter(this,strings);        listView.setAdapter(commonAdapter);        listView.setOnRefreshListener(new CustomSearchListView.OnRefreshListener() {            @Override            public void onDownPullRefresh() {                new Thread(new Runnable() {                    @Override                    public void run() {                        try {                            Thread.sleep(1000);                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                        listView.hideHeaderView();                    }                }).start();            }        });        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {            @Override            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {                Methods.toast(CustomSearchListViewTestActivity.this,id+"");            }        });    }    @Override    protected int onSetContainerViewId() {        return R.layout.activity_custom_search_listview;    }    @Override    public void initListener() {    }    @Override    public void onClick(View v) {    }}
0 0
原创粉丝点击