效果圖需要實現一個類似於支付寶首頁,向上滑動,在頂部顯示懸浮框的效果,並且有淡入淡出動畫效果,本來以爲使用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);
}
}