仿今日头条下拉出现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) { }}
- 仿今日头条下拉出现SearchBar,再下拉刷新效果,SearchListView实现以及原理讲解
- 仿今日头条下拉出现SearchBar,再下拉刷新效果,SearchListView实现以及原理讲解
- 仿今日头条下拉出现SearchBar,再下拉刷新效果,SearchListView实现以及原理讲解
- android仿今日头条下拉刷新中的vector动画
- Android使用SVG实现今日头条下拉刷新动画
- 仿IOS版QQ的下拉刷新头实现原理
- 仿今日头条TabLayout,侧滑,上拉加载下拉刷新
- Android高仿今日头条/QQ空间手势下拉关闭图片效果
- 【Android】仿今日头条简单的刷新效果
- TabLayout实现仿今日头条顶部tab导航效果
- 仿今日头条滑动评论效果
- 仿今日头条顶部导航效果
- 仿今日头条视频显示效果
- Android自定义ListView实现下拉刷新,效果仿SwipeRefreshLayout
- android 仿朋友圈下拉刷新效果
- 仿饿了么下拉刷新效果
- 仿今日头条
- 仿今日头条
- line-height
- ps一些用法记录
- 山东省第五届ACM省赛题——angry_birds_again_and_again(计算几何)
- Reactor与Proactor的概念
- css <font>标签让一行字有大有小的显示
- 仿今日头条下拉出现SearchBar,再下拉刷新效果,SearchListView实现以及原理讲解
- Problem1013
- 二叉树上最长单色路径
- EditText限制、String切割
- java字符串翻转
- 每日随笔小记-4月20日
- C# Directory操作
- Ftxxxx系列--------kernel中传值
- ETL技术入门之发送邮件(二)