效果图需要实现一个类似于支付宝首页,向上滑动,在顶部显示悬浮框的效果,并且有淡入淡出动画效果,本来以为使用CoordinatorLayout可以实现,结果效果不理想。参考博客Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果,将代码进行改造,加入了淡入淡出效果。在实现过程中,有3个问题:
1.滑动过快,显示异常,解决方法是将MyScrollView类中的onTouchEvent中的5秒改成20秒,现在没发现有问题
/**
* 重写onTouchEvent, 当用户的手在MyScrollView上面的时候,
* 直接将MyScrollView滑动的Y方向距离回调给onScroll方法中,当用户抬起手的时候,
* MyScrollView可能还在滑动,所以当用户抬起手我们隔5毫秒给handler发送消息,在handler处理
* MyScrollView滑动的距离
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(onScrollListener != null){
onScrollListener.onScroll(lastScrollY = this.getScrollY());
}
switch(ev.getAction()){
case MotionEvent.ACTION_UP:
handler.sendMessageDelayed(handler.obtainMessage(), 20);
break;
}
return super.onTouchEvent(ev);
}
2.因为弹出框出现和隐藏,都是根据控件是否为null来判断,在隐藏的时候,会将控件null,在做动画的时候,在onAnimationEnd方法里将view设为null,会出现异常。最后决定使用标志位来判断,不将弹出框view设为null
3.弹出框在我的模拟器上会报权限错误,所以我在真机上测试
修改后的代码
MyScrollView
public class MyScrollView extends ScrollView {
private OnScrollListener onScrollListener;
/**
* 主要是用在用户手指离开MyScrollView,MyScrollView还在继续滑动,我们用来保存Y的距离,然后做比较
*/
private int lastScrollY;
public MyScrollView(Context context) {
this(context, null);
}
public MyScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 设置滚动接口
* @param onScrollListener
*/
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
/**
* 用于用户手指离开MyScrollView的时候获取MyScrollView滚动的Y距离,然后回调给onScroll方法中
*/
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
int scrollY = MyScrollView.this.getScrollY();
//此时的距离和记录下的距离不相等,在隔5毫秒给handler发送消息
if(lastScrollY != scrollY){
lastScrollY = scrollY;
handler.sendMessageDelayed(handler.obtainMessage(), 5);
}
if(onScrollListener != null){
onScrollListener.onScroll(scrollY);
}
};
};
/**
* 重写onTouchEvent, 当用户的手在MyScrollView上面的时候,
* 直接将MyScrollView滑动的Y方向距离回调给onScroll方法中,当用户抬起手的时候,
* MyScrollView可能还在滑动,所以当用户抬起手我们隔5毫秒给handler发送消息,在handler处理
* MyScrollView滑动的距离
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(onScrollListener != null){
onScrollListener.onScroll(lastScrollY = this.getScrollY());
}
switch(ev.getAction()){
case MotionEvent.ACTION_UP:
handler.sendMessageDelayed(handler.obtainMessage(), 20);
break;
}
return super.onTouchEvent(ev);
}
/**
*
* 滚动的回调接口
*
* @author xiaanming
*
*/
public interface OnScrollListener{
/**
* 回调方法, 返回MyScrollView滑动的Y方向距离
* @param scrollY
* 、
*/
public void onScroll(int scrollY);
}
}
主界面中,在代码中对一些控件重新绘制尺寸,所以,在绘制结束后,要重新获取决定是否显示对话框控件的位置和尺寸
bannerView控件初始是隐藏的,在代码中重新绘制,然后从后台获取数据,获取成功,则显示。所以在显示的时候,要重新获取llHealthDatas控件的位置
bannerView.setVisibility(View.GONE);
adBanner = new ADBanner(adViewpager, getActivity(), adPoints);
initModuleData();
makeHealthNews();
mWindowManager = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
//设置图片等比例缩放
final DisplayMetrics metric = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(metric);
screenWidth = metric.widthPixels; // 屏幕宽度(像素)
final ViewGroup.LayoutParams lp = bannerView.getLayoutParams();
lp.width = screenWidth;
// lp.height = LinearLayout.LayoutParams.WRAP_CONTENT;
lp.height = 275 * screenWidth / 750;
bannerView.setLayoutParams(lp);
llHealthDatas.post(new Runnable()
{
@Override
public void run()
{
showSuspendViewHeight = llHealthDatas.getHeight();
showSuspendViewTop = llHealthDatas.getTop();
}
});
scrollview.post(new Runnable()
{
@Override
public void run()
{
myScrollViewTop = scrollview.getTop();
}
});
scrollview.setOnScrollListener(this);
......................................................................
if (data.isOK())
{
bannerView.setVisibility(View.VISIBLE);
adBanner.createOrUpdateADDatas(data.getRstData());
llHealthDatas.post(new Runnable()
{
@Override
public void run()
{
showSuspendViewHeight = llHealthDatas.getHeight();
showSuspendViewTop = llHealthDatas.getTop();
}
});
}
显示和隐藏弹出框
/*********************************************/
/**
* 以下参数定义:滑动到某一控件,来决定是否显示悬浮框
*/
/**
* 悬浮框View
*/
private static View suspendView;
/**
* 悬浮框的参数
*/
private static WindowManager.LayoutParams suspendLayoutParams;
/**
* 决定悬浮框是否显示的布局的高度
*/
private int showSuspendViewHeight;
/**
* myScrollView与其父类布局的顶部距离
*/
private int myScrollViewTop;
/**
* 决定悬浮框是否显示的布局与其父类布局的顶部距离
*/
private int showSuspendViewTop;
private boolean isSuspendViewShow = false;
private int lastScroll=0;
/**
* 滚动的回调方法,当
* 滚动的Y距离大于或者等于显示布局距离父类布局顶部的位置,就显示悬浮框
* 当滚动的Y的距离小于 显示布局距离父类布局顶部的位置加上显示布局的高度就移除购买的悬浮框
*/
/**
* 滚动的回调方法,当滚动的Y距离大于或者等于显示布局距离父类布局顶部的位置,就显示悬浮框
* 当滚动的Y的距离小于 显示布局距离父类布局顶部的位置加上显示布局的高度就移除购买的悬浮框
*/
@Override
public void onScroll(int scrollY)
{
lastScroll=scrollY;
if (scrollY >= showSuspendViewTop)
{
showSuspend();
}
else if (scrollY <= showSuspendViewTop + showSuspendViewHeight)
{
removeSuspend();
}
}
/**
* 显示悬浮框
*/
private void showSuspend()
{
if (suspendView == null)
{
suspendView = LayoutInflater.from(getActivity()).inflate(R.layout.home_suspension_dialog, null);
if (suspendLayoutParams == null)
{
suspendLayoutParams = new WindowManager.LayoutParams();
suspendLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
suspendLayoutParams.format = PixelFormat.RGBA_8888;
suspendLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
suspendLayoutParams.gravity = Gravity.TOP;
suspendLayoutParams.width = screenWidth;
suspendLayoutParams.height = showSuspendViewHeight;
suspendLayoutParams.x = 0;
suspendLayoutParams.y = myScrollViewTop;
}
mWindowManager.addView(suspendView, suspendLayoutParams);
}
suspendView.setAlpha(0);
suspendView.animate().alpha(1f).setDuration(500).setListener(null);
isSuspendViewShow = true;
}
/**
* 移除悬浮框
*/
private void removeSuspend()
{
if (isSuspendViewShow)
{
if (suspendView != null)
{
suspendView.setAlpha(1);
suspendView.animate().alpha(0f).setDuration(500).setListener(null);
isSuspendViewShow = false;
}
}
}
因为程序是在fragment中,所以涉及到fragment的切换
@Override
public void setUserVisibleHint(boolean isVisibleToUser)
{
super.setUserVisibleHint(isVisibleToUser);
//切换fragment,在本fragment消失以后,弹出框消失,重新显示本fragment,
// 如果滑动停留位置是需要显示弹出框的,则重新显示
if(!isVisibleToUser){
if(suspendView!=null){
suspendView.setVisibility(View.INVISIBLE);
}
}else{
if(suspendView!=null){
suspendView.setVisibility(View.VISIBLE);
}
}
}
ps:做完项目之后,觉得其实还有一个简单的办法来实现,思路就是将悬浮框布局,放在主布局中,根据滑动来决定展示和隐藏。就是悬浮框布局和主布局重叠,使用FragmentLayout或者其他的布局
修改过的MyScrollView
public class MyScrollView extends ScrollView
{
private OnScrollListener onScrollListener;
private Handler listenerHandler = null;
//private int mLastY = 0;
/**
* 主要是用在用户手指离开MyScrollView,MyScrollView还在继续滑动,我们用来保存Y的距离,然后做比较
*/
private int lastScrollY;
public MyScrollView(Context context)
{
this(context, null);
}
public MyScrollView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
listenerHandler = new Handler();
}
/**
* 设置滚动接口
*
* @param onScrollListener
*/
public void setOnScrollListener(OnScrollListener onScrollListener)
{
this.onScrollListener = onScrollListener;
}
// /**
// * 用于用户手指离开MyScrollView的时候获取MyScrollView滚动的Y距离,然后回调给onScroll方法中
// */
// private Handler handler = new Handler()
// {
//
// public void handleMessage(android.os.Message msg)
// {
// int scrollY = MyScrollView.this.getScrollY();
//
// //此时的距离和记录下的距离不相等,在隔5毫秒给handler发送消息
// if (lastScrollY != scrollY)
// {
// lastScrollY = scrollY;
// handler.sendMessageDelayed(handler.obtainMessage(), 5);
// }
// if (onScrollListener != null)
// {
// onScrollListener.onScroll(scrollY);
// }
// }
//
// };
//
// /**
// * 重写onTouchEvent, 当用户的手在MyScrollView上面的时候,
// * 直接将MyScrollView滑动的Y方向距离回调给onScroll方法中,当用户抬起手的时候,
// * MyScrollView可能还在滑动,所以当用户抬起手我们隔5毫秒给handler发送消息,在handler处理
// * MyScrollView滑动的距离
// */
// @Override
// public boolean onTouchEvent(MotionEvent ev)
// {
// if (onScrollListener != null)
// {
// onScrollListener.onScroll(lastScrollY = this.getScrollY());
// }
// switch (ev.getAction())
// {
// case MotionEvent.ACTION_UP:
// handler.sendMessageDelayed(handler.obtainMessage(), 20);
// break;
// }
// return super.onTouchEvent(ev);
// }
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy)
{
super.onScrollChanged(x, y, oldx, oldy);
// if (onScrollListener != null)
// {
// onScrollListener.onScrollChanged(this, x, y, oldx, oldy);
// }
listenerHandler.post(new ScrollChangedRunnable(x, y, oldx, oldy));
}
public int getTotalVerticalScrollRange()
{
return computeVerticalScrollRange();
}
@Override
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect)
{
return 0;
}
private class ScrollChangedRunnable implements Runnable
{
private int x;
private int y;
private int oldx;
private int oldy;
public ScrollChangedRunnable(int x, int y, int oldx, int oldy)
{
this.x = x;
this.y = y;
this.oldx = oldx;
this.oldy = oldy;
}
@Override
public void run()
{
if (onScrollListener != null)
{
onScrollListener.onScrollChanged(MyScrollView.this, x, y, oldx, oldy);
}
}
}
/**
* 滚动的回调接口
*
* @author xiaanming
*/
public interface OnScrollListener
{
// /**
// * 回调方法, 返回MyScrollView滑动的Y方向距离
// *
// * @param scrollY 、
// */
// public void onScroll(int scrollY);
public void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy);
}
}