本博客由gengqiquan原创,转载请注明出处http://blog.csdn.net/gengqiquan/article/details/50542334尊重他人的技术劳动成果,谢谢
UI妹子是个喜欢看各种软件的妹子,这不,最近看上了UC的首页,于是把我们的应用也改成了这样的风格,本着不重复造轮子的原则,网上百度了一大堆,全特么没有用。好吧,不重复造轮子不代表自己不去制造轮子,现在我们来自己造一个。
先看效果(不贴效果寡代码的人太可恶了,复制粘贴后才发现不是自己要的,简直浪费宝贵时间)
图片有点小,没办法,CSDN最大只能传2mb,随便转换下都不止,只好用小图了。
效果应该还可以,要实现上面的效果,我们需要做哪些事呢?
首先,我们得有一个容器,由布局可知容器最好是线性的,这里我们用LinearLayout,然后我们是滑动隐藏,要让LinearLayout有平滑的滑动效果,我们就要用到Scroller,不清楚Scroller的同学可以自行百度,我这里就不贴链接了,网上一大堆。滑动的代码基本都这样
private void smoothScrollTo(int dx, int dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy - getScrollY());
invalidate();
if (dy == 0) {//这里可以判断滑动完成后顶部是隐藏还是现实,在回调中可以控制界面的底部的展现和隐藏,比如改变tab的透明度什么的
mShowing = true;
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(true);
} else if (dy == Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(false);
mShowing = false;
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
恩,然后我们是滑动隐藏,必然涉及到触摸事件,这里我们重写OnTouchEvent()函数来实现滑动
@Override
public boolean onTouchEvent(MotionEvent ev) {
int scrollY = getScrollY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
oldY = ev.getY();
mDLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int mSlid = (int) (ev.getY() - mDLastY);
if (mSlid < 0 && scrollY < Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.onScoll(-mSlid, Max);
scrollBy(0, -mSlid);
}
break;
case MotionEvent.ACTION_UP:
if (scrollY > 0)
if (Math.abs(scrollY) < Max / 2) {
smoothScrollTo(0, 0);
} else {
smoothScrollTo(0, Max);
}
break;
}
mDLastY = ev.getY();
return true;
}
这里没有注释,稍微讲解下, 我们在MotionEvent.ACTION_DOWN里获取手指的按下位置,然后再在ACTION_MOVE里用当前的手指Y坐标与之前的Y坐标作比较,当他在0-Max之间时响应滑动,这里的Max是顶部待隐藏的布局的高度,值的获取下面我们再讲,最后我们在ACTION_UP里判断滑动的距离scrollY,这个值是有正有负的。代表往不同的方向滑动,在有滑动的情况下我们与Max的一半高度作比较,大于他我们就隐藏,小于就恢复到初始状态,最后的最后不要忘了mDLastY = ev.getY();这一句,记录下当前的Y坐标作为下一次进入onTouchEvent()时的上一次Y坐标记录。
经过以上代码。我们实现了如何使一个LinearLayout可以响应手指进行滑动,但我们上面的效果其实不仅仅是滑动隐藏,还要响应下面的tab选项卡的水平滑动,并且当顶部隐藏时可以单个列表可以下拉刷新,那种一次下拉刷新所有列表的坑用户流量的反人类行为我们是不做的,那么如何做到只响应上滑隐藏的事件的同时响应底部各种的滑动切换和刷新事件呢?
这里我们决定使用事件分发机制里的第二种方式,父容器拦截法,我们来重写onInterceptTouchEvent()方法。代码如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean indelet = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
indelet = false;
if (!mScroller.isFinished()) {// 保证停止动画防止冲突
mScroller.abortAnimation();
indelet = true;
}
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mlastinterceptx;
float dy = ev.getY() - mlastinterceptY;
// 横向滑动
if (Math.abs(dx) >= Math.abs(dy)) {
indelet = false;
} else {// 竖向滑动
if (mShowing) {
if (dy < 0)// 这里我们只需要响应竖向滑动的上滑;
indelet = true;
} else {
indelet = false;
}
}
break;
case MotionEvent.ACTION_UP:
indelet = false;
break;
}
mlastinterceptx = (int) ev.getX();
mlastinterceptY = (int) ev.getY();
mDLastY = ev.getY();
return indelet;
}
核心代码就在ACTION_MOVE时
if (Math.abs(dx) >= Math.abs(dy)) {// 横向滑动
indelet = false;
} else {// 竖向滑动
if (mShowing) {
if (dy < 0)// 这里我们只需要响应竖向滑动的上滑;
indelet = true;
} else {
indelet = false;
}
}
我们只要响应向上滑动就可以了,所以遇到横向滑动时直接传递给子控件,然后当为上下滑动时,我们判断滑动值,小于0时为向上,我们截取这个时候的触摸事件。
千万记住ACTION_DOWN的时候如果非在动画进行时一定要返回false,否则下面的所有事件直接都由当前父容器处理,内部的控件接受不到任何触摸事件了。
最后我们来看下如何测量顶部布局高度Max。
很简单,重写onMeasure(),就好,第一次进入onMeasure()时获取到顶部的高度,当然我们也可以写个public方法来在外部设置Max的大小,不过我懒,而且我也不知道以后接手这个项目的人看了这个控件是否还记得调用这个方法,到时候没效果岂不是要喷死我。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
isMeasured = true;
topView = getChildAt(0);
bottomView = getChildAt(1);
Max = topView.getMeasuredHeight();
}
bottomView.measure(widthMeasureSpec, heightMeasureSpec);
}
以上就是整个效果的完整实现流程了,贴一下完整的代码
控件类的代码
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Scroller;
import com.umeng.socialize.utils.Log;
// TODO: Auto-generated Javadoc
/**
*
*
* @author gengqiquan
* @version v1.0
* @date:2016年1月19日13:48:13
*/
public class UpHideScrollView extends LinearLayout {
int Max = 0;//顶部待隐藏的高度
private boolean isMeasured = false;//是否第一次测量大小
private View topView, bottomView;
public boolean mShowing = true;
float oldY, mDLastY;
Scroller mScroller;
int mlastinterceptx, mlastinterceptY;
OnHeaderScrollLiesten onHeaderScrollLiesten;
public UpHideScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
setScrollBarStyle(SCROLL_AXIS_NONE);
mScroller = new Scroller(context);
}
private void smoothScrollTo(int dx, int dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy - getScrollY());
invalidate();
if (dy == 0) {//这里可以判断滑动完成后顶部是隐藏还是现实,在回调中可以控制界面的底部的展现和隐藏,比如改变tab的透明度什么的
mShowing = true;
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(true);
} else if (dy == Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(false);
mShowing = false;
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean indelet = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
indelet = false;
if (!mScroller.isFinished()) {// 保证停止动画防止冲突
mScroller.abortAnimation();
indelet = true;
}
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mlastinterceptx;
float dy = ev.getY() - mlastinterceptY;
// 横向滑动
if (Math.abs(dx) >= Math.abs(dy)) {
indelet = false;
} else {// 竖向滑动
if (mShowing) {
if (dy < 0)// 这里我们只需要响应竖向滑动的上滑;
indelet = true;
} else {
indelet = false;
}
}
break;
case MotionEvent.ACTION_UP:
indelet = false;
break;
}
mlastinterceptx = (int) ev.getX();
mlastinterceptY = (int) ev.getY();
mDLastY = ev.getY();
return indelet;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int scrollY = getScrollY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
oldY = ev.getY();
mDLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int mSlid = (int) (ev.getY() - mDLastY);
if (mSlid < 0 && scrollY < Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.onScoll(-mSlid, Max);
scrollBy(0, -mSlid);
}
break;
case MotionEvent.ACTION_UP:
if (scrollY > 0)
if (Math.abs(scrollY) < Max / 2) {
smoothScrollTo(0, 0);
} else {
smoothScrollTo(0, Max);
}
break;
}
mDLastY = ev.getY();
return true;
}
float firstY,sendLastY;
public void sendEvent(MotionEvent ev) {
int scrollY =(int) (ev.getY() - firstY);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
firstY = ev.getY();
sendLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (!mShowing) {
int mSlid = (int) (ev.getY() - sendLastY);
if (mSlid >0 && scrollY < Max&&mSlid<Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.onScoll(-mSlid, Max);
scrollBy(0, -mSlid);
}
}
break;
case MotionEvent.ACTION_UP:
if (scrollY > 0) {
if (Math.abs(scrollY) < Max / 2) {
smoothScrollTo(0, Max);
} else {
smoothScrollTo(0, 0);
}
}
break;
}
sendLastY = ev.getY();
}
public void setOnHeaderScrollLiesten(OnHeaderScrollLiesten liesten) {
onHeaderScrollLiesten = liesten;
}
public interface OnHeaderScrollLiesten {
void onScoll(int y, int h);
void isCompute(boolean b);//
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
super.dispatchTouchEvent(ev);
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b + Max);
}
int topHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
isMeasured = true;
topView = getChildAt(0);
bottomView = getChildAt(1);
Max = topView.getMeasuredHeight();
}
bottomView.measure(widthMeasureSpec, heightMeasureSpec);
}
public void ShowHeader() {
smoothScrollTo(0, 0);
}
public void ShowHeaderDelay() {
mScroller.startScroll(getScrollX(), getScrollY(), 0, 0 - getScrollY(), 2000);
invalidate();
mShowing = true;
}
public void HideHeader() {
smoothScrollTo(0, Max);
}
}
activity布局的代码
<com.qbs.itrytryc.views.UpHideScrollView
android:id="@+id/uphide"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.qbs.itrytryc.views.TurnView
android:id="@+id/turnView"
android:layout_width="match_parent"
android:layout_height="168dp" />
<com.qbs.itrytryc.views.SlidingFixTabView
android:id="@+id/slid"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.qbs.itrytryc.views.UpHideScrollView>
activity中调用的代码
hideScrollView.setOnHeaderScrollLiesten(new OnHeaderScrollLiesten() {
@Override
public void onScoll(int y, int h) {
}
@Override
public void isCompute(boolean Showing) {
if (!Showing) {//这里通知界面做一些响应隐藏的事件,比如隐藏底部tab
EventBus.getDefault().post(new IntEvent());
}
}
});
}
直接调用代码显示头部
upHideScrollView.ShowHeaderDelay();
监听listview的滚动自动显示头部
listView.setOnTouchListener(new View.OnTouchListener() {
float mDLastY;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getY() - mDLastY > 0) {
if (listView.getFirstVisiblePosition() == 0)
upHideScrollView.sendEvent(event);
}
return false;
}
});
好了,祝大家新年快乐。
补充更新:一直有人找我要源码,其实这篇博客里的代码就是源码。已经很全了,有些人估计想要的是能运行的demo。问题这是我很久前的公司项目。项目代码已经交接了,我是不会保存的。凑巧上个月新公司也有个类似的需求效果,于是我再次写了个完整的控件,并且增加了下滑显示功能。这次上传到资源库方便那些实在懒得要死的人,不过收5分
上滑隐藏下滑显示控件
最后再重申一遍,博客代码就是源码
我建了一个QQ群(群号:121606151),用于大家讨论交流Android技术问题,有兴趣的可以加下,大家一起进步。