最近刚辞职,找工作也是焦头烂额啊。大家都说,android的职位最近不好找。经过我这几个星期的体验,主要是面试的机会都没有,市场供过于求!所以,投了简历,然后在家等面试通知,就做了一个自定义ScollView,我发现市场上很多项目的标题栏,都是根据ScollView的滚动,然后动态改变标题栏。例如:拉钩,淘宝,华为应用
先来看个效果,当滑到顶部,滑过在ViewPage上的搜索框是,顶部的绿色搜索框就会隐藏。如果屏幕往上滚,滚过了ViewPage上的搜索框,顶部的绿色搜索框就会出来。另外,还增加了可以整一个布局根据手势触摸拖拽
头部搜索框实现的原理
1.先拿到ViewPage上的搜索框的left,top,right,bottom这几个的位置数值
//用于保存原始的搜索框布局的位置
private Rect normalSearch = new Rect();
if(normalSearch.isEmpty()){
//获取到对应控件的位置相应的数值
normalSearch.set(innerView.getLeft(), innerView.getTop(),
innerView.getRight(), innerView.getBottom());
}
2.获取到搜索框的位置值后,就可以去监听scollview的滚动距离了
/**
* 滚动监听
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
// TODO Auto-generated method stub
super.onScrollChanged(l, t, oldl, oldt);
//对外开放接口,实现监听滚动监听
onScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
Log.e("Ruan", "距离顶部的距离:"+t);
Log.e("Ruan", "测量的传进来的View高度:"+normalSearch.top);
if(t-normalSearch.top<0||t==normalSearch.top){//如果滑到顶部就隐藏edt
onScrollChangeListener.onScrollTopListener();//监听滚动到相应的位置
// edt.setVisibility(View.GONE);
innerView.setVisibility(View.VISIBLE);
}else if(t-normalSearch.top>0){
onScrollChangeListener.onScrollBottomListener();//监听滚动到相应的位置
// edt.setVisibility(View.VISIBLE);
innerView.setVisibility(View.GONE);
}
}
3.然后再去写一个滚动监听的接口,给外部提供调用,然就可以根据滚动的距离,实现对头部的相应操作
/**
*
* 对滚动进行监听,提供给外部调用
* @author ruan
*
*/
public interface OnScrollChangeListener {
void onScrollChange(MyScrollview view, int x, int y, int oldx, int oldy);
void onScrollBottomListener();
void onScrollTopListener();
}
整个布局拖拽的原理
1.第一步需要先实现OnTouchEvent触摸事件
/**
* 复写ontouchEvent触摸事件,这个是主要的
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
//从外部传进来一个id,获取你要定位的控件
innerView = view.findViewById(RID);
if (innerView == null) {
//如果获取不到控件,就直接返回父类的ontouch
return super.onTouchEvent(ev);
} else {
if(normalSearch.isEmpty()){
//获取到对应控件的位置相应的数值
normalSearch.set(innerView.getLeft(), innerView.getTop(),
innerView.getRight(), innerView.getBottom());
}
//具体消费的触摸事件操作
commonTouchEvent(ev);
}
return super.onTouchEvent(ev);
}
2.再在相应的action动作中对布局实现控制
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//按下时,获取到y值
y = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//先获取按下时的坐标
float preY = y == 0 ? ev.getY() : y;
//获取滑动的距离
float nowY = ev.getY();
//两个相减就得到,滑动的距离
int detailY = (int) (preY - nowY);
//把当前的坐标,赋值给原始的坐标y
y = nowY;
//操作view进行拖动detailY的一半
if (isNeedMove()) {//根据当前的布局view与scollview滚动距离,判断是否需要改变布局view的距离
//布局改变位置之前,记录一下正常状态的位置
if (normal.isEmpty()) {
normal.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
}
view.layout(view.getLeft(), view.getTop() - detailY / 2, view.getRight(), view.getBottom() - detailY / 2);
}
break;
case MotionEvent.ACTION_UP:
y = 0;
//布局回滚到原来的位置
if (isNeedAnimation()) {
animation();
}
break;
}
3.实现布局回滚的动画
/**
* 执行回滚动画
*/
private void animation() {
TranslateAnimation ta = new TranslateAnimation(0, 0, 0, normal.top - view.getTop());
ta.setDuration(200);
//进行动画监听
ta.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
//标记动画进行时,不重复执行动画
animationFinish = false;
}
@Override
public void onAnimationEnd(Animation animation) {
//动画结束,清除动画,view布局回到原来的位置
view.clearAnimation();
view.layout(normal.left, normal.top, normal.right, normal.bottom);
normal.setEmpty();
animationFinish = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(ta);
}
4.在这里为止就可以实现整个布局拖拽回滚,完整的代码如下
package com.ruan.sample.view;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.EditText;
import android.widget.ScrollView;
/**
* Created by Administrator on 2017/05/04.
* 自定义scollview实现根据scollview滚动,改变头部显示方式
*/
public class MyScrollview extends ScrollView {
//要操作的整个ScollView布局
private View view;
//要在操作的搜素框的View
private View innerView;
private float y;
//用于保存原始ScollView布局的位置
private Rect normal = new Rect();
//用于保存原始的搜索框布局的位置
private Rect normalSearch = new Rect();
private boolean animationFinish = true;
//scollview滚动监听
private OnScrollChangeListener onScrollChangeListener;
private EditText edt;
private int RID;
public MyScrollview(Context context) {
super(context, null);
}
public void setOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener){
this.onScrollChangeListener = onScrollChangeListener;
}
public MyScrollview(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void getRID(int RID){
this.RID = RID;
}
public void getEdt(EditText edt){
this.edt = edt;
}
/**
* 滚动监听
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
// TODO Auto-generated method stub
super.onScrollChanged(l, t, oldl, oldt);
//对外开放接口,实现监听滚动监听
onScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
Log.e("Ruan", "距离顶部的距离:"+t);
Log.e("Ruan", "测量的传进来的View高度:"+normalSearch.top);
if(t-normalSearch.top<0||t==normalSearch.top){//如果滑到顶部就隐藏edt
onScrollChangeListener.onScrollTopListener();//监听滚动到相应的位置
// edt.setVisibility(View.GONE);
innerView.setVisibility(View.VISIBLE);
}else if(t-normalSearch.top>0){
onScrollChangeListener.onScrollBottomListener();//监听滚动到相应的位置
// edt.setVisibility(View.VISIBLE);
innerView.setVisibility(View.GONE);
}
}
@Override
protected void onFinishInflate() {
int childCount = getChildCount();
if (childCount > 0) {
// innerView = getChildAt(0);
//获取最外层的布局
view = getChildAt(0);
}
}
/**
* 复写ontouchEvent触摸事件,这个是主要的
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
//从外部传进来一个id,获取你要定位的控件
innerView = view.findViewById(RID);
if (innerView == null) {
//如果获取不到控件,就直接返回父类的ontouch
return super.onTouchEvent(ev);
} else {
if(normalSearch.isEmpty()){
//获取到对应控件的位置相应的数值
normalSearch.set(innerView.getLeft(), innerView.getTop(),
innerView.getRight(), innerView.getBottom());
}
//具体消费的触摸事件操作
commonTouchEvent(ev);
}
return super.onTouchEvent(ev);
}
/**
* 自定义touch事件处理
*
* @param ev
*/
private void commonTouchEvent(MotionEvent ev) {
if (animationFinish) {
//上一次动画完成后才继续执行新的动画(防止冲突)
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//按下时,获取到y值
y = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//先获取按下时的坐标
float preY = y == 0 ? ev.getY() : y;
//获取滑动的距离
float nowY = ev.getY();
//两个相减就得到,滑动的距离
int detailY = (int) (preY - nowY);
//把当前的坐标,赋值给原始的坐标y
y = nowY;
//操作view进行拖动detailY的一半
if (isNeedMove()) {//根据当前的布局view与scollview滚动距离,判断是否需要改变布局view的距离
//布局改变位置之前,记录一下正常状态的位置
if (normal.isEmpty()) {
normal.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
}
view.layout(view.getLeft(), view.getTop() - detailY / 2, view.getRight(), view.getBottom() - detailY / 2);
}
break;
case MotionEvent.ACTION_UP:
y = 0;
//布局回滚到原来的位置
if (isNeedAnimation()) {
animation();
}
break;
}
}
}
/**
* 执行回滚动画
*/
private void animation() {
TranslateAnimation ta = new TranslateAnimation(0, 0, 0, normal.top - view.getTop());
ta.setDuration(200);
//进行动画监听
ta.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
//标记动画进行时,不重复执行动画
animationFinish = false;
}
@Override
public void onAnimationEnd(Animation animation) {
//动画结束,清除动画,view布局回到原来的位置
view.clearAnimation();
view.layout(normal.left, normal.top, normal.right, normal.bottom);
normal.setEmpty();
animationFinish = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(ta);
}
/**
* 判断是否需要回滚
*
* @return
*/
private boolean isNeedAnimation() {
return !normal.isEmpty();
}
/**
* 判断是否需要移动
*
* @return
*/
private boolean isNeedMove() {
int offset = view.getMeasuredHeight() - getHeight();
float y = innerView.getY();
int scrollY = getScrollY();
Log.i("zoubo", "getMeasuredHeight:" + innerView.getMeasuredHeight() + "----getHeight:" + getHeight());
Log.i("zoubo", "offset:" + getHeight() + "----scrollY:" + scrollY);
if (scrollY == 0 || scrollY == offset) {
return true;
}
return false;
}
/**
*
* 对滚动进行监听,提供给外部调用
* @author ruan
*
*/
public interface OnScrollChangeListener {
void onScrollChange(MyScrollview view, int x, int y, int oldx, int oldy);
void onScrollBottomListener();
void onScrollTopListener();
}
}