自定義拉取選單
//1.引數類:
public class Constant {
public static int POI_DEFAULT_SHOW_HEIGHT_MID = 1200;//單位dp
public final static int POI_DEFAULT_SHOW_HEIGHT_BOTTOM = 35;//單位dp
}
//2.自定義選單
/**
* 可上下滑動的view
*
* @author dingys
* @description:
*/
public class CustomUpAndDownLayout extends RelativeLayout implements
OnTouchListener {
private Context mContext;
private int screenHeigh;
private VelocityTracker mVelocityTracker;
private int slideState;
private boolean isSliding;
private int moveMinValue = 20;// 單位dp,在做事件攔截時使用
private int defaultShowHeight = 200;// 單位dp
// 是否已經完全顯示了
private boolean isFullVisible = false;
/*
* 滾動顯示和隱藏上側佈局時,手指滑動需要達到的速度。
*/
public static final int SNAP_VELOCITY = 200;
/**
* 滑動狀態 表示未進行任何滑動。
*/
public static final int DO_NOTHING = 0;
/**
* 滑動狀態,表示正在往下滑動
*/
public static final int HIDING = 1;
/**
* 表示View 正在顯示
*/
public static final int SHOWING = 3;
/**
* 滑動狀態的一種,表示已經到達了底部時,繼續向下滑動。
*/
public static final int HIDEING_2 = 4;
private int touchSlop;//在被判定為滾動之前使用者手指可以移動的最大值。
private float xDown;
private float yDown;
private float xMove;
private float yMove;
private float yUp;
private LayoutParams contentLayoutParams;
private boolean once = false;
private int showMaxHeight;// 畫素
private UpAndDownListener1 upAndDownListener;
private boolean isSlide = true;
public CustomUpAndDownLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
public CustomUpAndDownLayout(Context context) {
super(context);
this.mContext = context;
init();
}
public void setListener(UpAndDownListener1 upAndDownListener) {
this.upAndDownListener = upAndDownListener;
}
public void setDefaultShowHeight(int showHeight) {
this.defaultShowHeight = showHeight;
}
public void isOnce(boolean once, boolean isFull) {
this.once = once;
this.isFullVisible = isFull;
}
// 當底部bar顯示或者隱藏時,重新設定this顯示的最大高度
public void setMaxHeight(int h) {
showMaxHeight = h;
// 當是全屏時重新設定它的高度(情況有:底部狀態列顯示隱藏,和橫豎屏切換)
if (isFullVisible) {
if (contentLayoutParams != null) {
contentLayoutParams.height = h;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
CustomUpAndDownLayout.this
.setLayoutParams(contentLayoutParams);
}
}, 10);
}
}
}
public int getShowMaxH() {
return showMaxHeight;
}
int n = 0;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!once) {
// 橫豎屏切換時重新設定isFullVisible=false
if (contentLayoutParams != null) {// 重新設定高度
contentLayoutParams.height = Utils.dp2Px(mContext,
defaultShowHeight);
this.setLayoutParams(contentLayoutParams);
}
// 重新設定up and down icon
if (upAndDownListener != null) {
if (isFullVisible) {
upAndDownListener.onOpen();
} else {
upAndDownListener.onClose();
}
}
once = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int arg1, int arg2, int arg3,
int arg4) {
contentLayoutParams = (LayoutParams) this.getLayoutParams();
super.onLayout(changed, arg1, arg2, arg3, arg4);
}
private void init() {
screenHeigh = Utils.getScreenHeight(mContext);
showMaxHeight = screenHeigh;
setOnTouchListener(this);
touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
/**
* 設定是否允許滑動
*
* @param isSlide
*/
public void isSlide(boolean isSlide) {
this.isSlide = isSlide;
}
public boolean isCanSlide() {
return isSlide;
}
private boolean isScrolling = false;
public float downY;// 擡起時判斷滑動距離時使用
@Override
public boolean onTouch(View view, MotionEvent event) {
if (!isSlide) {
return false;
}
createVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 手指按下時,記錄按下時的座標
xDown = event.getRawX();
yDown = event.getRawY();
downY = event.getRawY();
// 將滑動狀態初始化為DO_NOTHING
slideState = DO_NOTHING;
// currTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
if (xDown == 0 && yDown == 0) {
xDown = event.getRawX();
yDown = event.getRawY();
downY = event.getRawY();
} else {
xMove = event.getRawX();
yMove = event.getRawY();
int moveDistanceX = (int) (xMove - xDown);
int moveDistanceY = (int) (yMove - yDown);
// 當預設是要顯示全屏時,這個地方獲取到的高度是-1(原因不清楚)
if (contentLayoutParams.height == -1) {
contentLayoutParams.height = showMaxHeight;
}
int tempheight = contentLayoutParams.height;// 得當前Layout距離頂部的距離
// 當佈局已經到達底部或者已經到達頂部時,移動無效
if ((tempheight >= showMaxHeight && moveDistanceY < 0)
|| (tempheight <= Utils.dp2Px(mContext,
defaultShowHeight) && moveDistanceY > 0)) {
break;
}
// 檢查當前的滑動狀態
checkSlideState(moveDistanceX, moveDistanceY);
switch (slideState) {
case HIDING:
contentLayoutParams.height = showMaxHeight - moveDistanceY;
setLayoutParams(contentLayoutParams);
break;
case HIDEING_2:
case SHOWING:
int height = contentLayoutParams.height;
contentLayoutParams.height = height - moveDistanceY;
setLayoutParams(contentLayoutParams);
yDown = event.getRawY();
break;
default:
break;
}
}
break;
case MotionEvent.ACTION_UP:
yUp = event.getRawY();
if (isSliding) {
// 手指擡起時,進行判斷當前手勢的意圖,這裡可根據當前的位置判斷顯示還是隱藏
switch (slideState) {
case HIDING:
case HIDEING_2:
if (shouldScrollToUpMenu()) {
scrollToUpMenu();
} else {
scrollToContentFromUpMenu();
}
break;
case SHOWING:
if (shouldScrollToContentFromUpMenu()) {
scrollToContentFromUpMenu();
} else {
scrollToUpMenu();
}
break;
default:
break;
}
}
recycleVelocityTracker();
break;
}
return true;
}
/*
* 將介面從底部介面滾動到內容介面,滾動速度設定為30.
*/
public void scrollToContentFromUpMenu() {
new UpMenuScrollTask().execute(300);
}
/**
* 判斷是否應該滾動將底部介面展示出來。如果手指移動距離大於螢幕寬度的1/4,或者手指移動速度大於SNAP_VELOCITY,
* 就認為應該滾動將底部介面展示出來。
*
* @return 如果應該將底部介面展示出來返回true,否則返回false。
*/
private boolean shouldScrollToUpMenu() {
return yUp - downY > 8;
}
/**
* 判斷是否應該從底部介面滾動到內容佈局,如果手指移動距離大於螢幕寬度的1/4,或者手指移動速度大於SNAP_VELOCITY,且必須是向上滾動的
* 就認為應該從底部介面滾動到內容佈局。
*
* @return 如果應該從底部介面滾動到內容佈局返回true,否則返回false。
*/
private boolean shouldScrollToContentFromUpMenu() {
float moveY = yUp - downY;
return (downY - yUp > screenHeigh / 4 || getScrollVelocity() > SNAP_VELOCITY)
&& moveY < 0;
}
/**
* 獲取手指在繫結佈局上的滑動速度。
*
* @return 滑動速度,以每秒鐘移動了多少畫素值為單位。
*/
private int getScrollVelocity() {
mVelocityTracker.computeCurrentVelocity(1000);
int velocity = (int) mVelocityTracker.getXVelocity();
return Math.abs(velocity);
}
/**
* 將介面滾動到底部介面介面,滾動速度設定為-30.
*/
public void scrollToUpMenu() {
new UpMenuScrollTask().execute(-300);
}
/**
* 檢查滑動狀態
*
* @param moveDistanceX
* @param moveDistanceY
*/
private void checkSlideState(int moveDistanceX, int moveDistanceY) {
if (!isFullVisible) {
// Y小於0 向上滑 Y 大於0 向下滑
if (!isSliding && Math.abs(moveDistanceY) >= touchSlop
&& moveDistanceY < 0) {
// 當在Y方向滑動的距離 大於手指滑動的最小距離 且向上滑動的時候
isSliding = true;
slideState = SHOWING;
} else if (!isSliding && Math.abs(moveDistanceY) >= touchSlop
&& moveDistanceY > 0) {
isSliding = true;
slideState = HIDEING_2;
}
} else {
// true是其全屏顯示的時候,此時向上滑動不起作用,因此只在向下滑動做處理,此處少考慮了一種情況,當向下滑動之後,再
if (!isSliding && Math.abs(moveDistanceY) >= touchSlop
&& moveDistanceY > 0 && Math.abs(moveDistanceX) < touchSlop) {
isSliding = true;
slideState = HIDING;
contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0);
contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
setLayoutParams(contentLayoutParams);
}
}
}
private void createVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
public boolean isShow() {
return isFullVisible;
}
/**
* 顯示
*/
public void showView() {
new UpMenuScrollTask().execute(50);
}
/**
* 隱藏
*/
public void hideView() {
new UpMenuScrollTask().execute(-50);
}
/**
* 切換
*/
public void toggle() {
if (isFullVisible) {
hideView();
} else {
showView();
}
}
class UpMenuScrollTask extends AsyncTask<Integer, Integer, Integer> {
@Override
protected Integer doInBackground(Integer... speed) {
contentLayoutParams = (LayoutParams) getLayoutParams();
int height = contentLayoutParams.height;
// 根據傳入的速度來滾動介面,當滾動到達邊界值時,跳出迴圈。
while (true) {
height = height + speed[0];
if (height >= showMaxHeight) {
height = showMaxHeight;
break;
}
if (height <= Utils.dp2Px(mContext, defaultShowHeight)) {
height = Utils.dp2Px(mContext, defaultShowHeight);
break;
}
isScrolling = true;
publishProgress(height);
// 為了要有滾動效果產生,每次迴圈使執行緒睡眠一段時間,這樣肉眼才能夠看到滾動動畫。
sleep(10);
}
if (speed[0] > 0) {
isFullVisible = true;
isScrolling = false;
} else {
isFullVisible = false;
}
slideState = DO_NOTHING;
isSliding = false;
return height;
}
@Override
protected void onProgressUpdate(Integer... height) {
contentLayoutParams.height = height[0];
setLayoutParams(contentLayoutParams);
unFocusBindView();
}
@Override
protected void onPostExecute(Integer height) {
contentLayoutParams.height = height;
setLayoutParams(contentLayoutParams);
if (upAndDownListener != null) {
if (isFullVisible) {
upAndDownListener.onOpen();
} else {
upAndDownListener.onClose();
}
}
}
}
private void unFocusBindView() {
setPressed(false);
setFocusable(false);
setFocusableInTouchMode(false);
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 回收VelocityTracker物件。
*/
private void recycleVelocityTracker() {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
float xDown1 = 0;
float yDown1 = 0;
boolean bScrollUpDown = false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
isScrolling = false;
xDown = 0;
yDown = 0;
xDown1 = ev.getX();
yDown1 = ev.getY();
bScrollUpDown = false;
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
float moveY = ev.getY();
float disX = moveX - xDown1;
float disY = moveY - yDown1;
if (Math.abs((int) disY) > Utils.dp2Px(mContext, moveMinValue)) {
bScrollUpDown = Math.abs(disX * 1000) < Math.abs(disY * 1000);
}
return bScrollUpDown;
case MotionEvent.ACTION_UP:
break;
}
boolean bRet = super.onInterceptTouchEvent(ev);
return bRet;
}
}
//3.自定義子佈局控制元件
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import com.bw.movie.utils.Utils;
public class InnerScrollView extends ScrollView {
/**
*/
private int moveMinValue = 20;
public RelativeLayout parentView;
public InnerScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private int lastScrollDelta = 0;
public void resume() {
overScrollBy(0, -lastScrollDelta, 0, getScrollY(), 0, getScrollRange(), 0, 0, true);
lastScrollDelta = 0;
}
int mTop = 10;
/**
* 將targetView滾到最頂端
*/
public void scrollTo(View targetView) {
int oldScrollY = getScrollY();
int top = targetView.getTop() - mTop;
int delatY = top - oldScrollY;
lastScrollDelta = delatY;
overScrollBy(0, delatY, 0, getScrollY(), 0, getScrollRange(), 0, 0, true);
}
private int getScrollRange() {
int scrollRange = 0;
if (getChildCount() > 0) {
View child = getChildAt(0);
scrollRange = Math.max(0, child.getHeight() - (getHeight()));
}
return scrollRange;
}
int currentY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (parentView == null) {
return super.onInterceptTouchEvent(ev);
} else {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 將父scrollview的滾動事件攔截
currentY = (int) ev.getY();
setParentScrollAble(false);
// Debuglog.i("innerScroll", "ACTION_DOWN");
return super.onInterceptTouchEvent(ev);
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
// 把滾動事件恢復給父Scrollview
setParentScrollAble(true);
// Debuglog.i("innerScroll", "ACTION_UP");
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
float moveY = ev.getY();
float dixY = moveY - currentY;
if (Math.abs(dixY) > Utils.dp2Px(getContext(), moveMinValue)) {
// Debuglog.i("innerScroll", "ACTION_MOVE");
return true;
}
//返回true把事件交給onTounch去處理
//return true;
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
View child = getChildAt(0);
try {
if (parentView != null) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
int height = child.getMeasuredHeight();
height = height - getMeasuredHeight();
// System.out.println("height=" + height);
int scrollY = getScrollY();
// System.out.println("scrollY" + scrollY);
int y = (int) ev.getY();
// 手指向下滑動
if (currentY < y) {
if (scrollY <= 0) {
// 如果向下滑動到頭,就把滾動交給父Scrollview
setParentScrollAble(true);
return false;
} else {
setParentScrollAble(false);
}
} else if (currentY > y) {
if (scrollY >= height) {
// 如果向上滑動到頭,就把滾動交給父Scrollview
setParentScrollAble(true);
return false;
} else {
setParentScrollAble(false);
}
}
currentY = y;
}
}
return super.onTouchEvent(ev);
} catch (IllegalArgumentException e) {
// TODO: handle exception
}
return false;
}
/**
* 是否把滾動事件交給父scrollview
*
* @param flag
*/
private void setParentScrollAble(boolean flag) {
parentView.requestDisallowInterceptTouchEvent(!flag);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if(listener!=null){
listener.onScrollChanged(l, t, oldl, oldt);
}
if(getScrollY() + getHeight() == computeVerticalScrollRange())
{
if (listener != null) {
int scrollY = getScrollY();
View childView=getChildAt(0);
if (scrollY==childView.getHeight()-getHeight()){
listener.scrollBottom();
}
}
}
}
ScrollViewListener listener;
public void setScrollViewListener(ScrollViewListener listener){
this.listener=listener;
}
public interface ScrollViewListener{
public void scrollBottom();
void onScrollChanged(int l, int t, int oldl, int oldt);
}
@Override
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
return 0;
}
}
//4.自定控制元件回撥監聽介面
public interface UpAndDownListener1 {
void onClose();
void onOpen();
void onAssignHeight(int height);
}
//使用方法
1、獲取控制元件
upAndDownView = (UpAndDownView) findViewById(R.id.up_down_view);
upAndDownView.setMaxHeight(Constant.POI_DEFAULT_SHOW_HEIGHT_MID);
final TextView tv_title = upAndDownView.findViewById(R.id.bottom_tv_title);
upAndDownView.showView();
upAndDownView.hideView();
2、設定顯示、隱藏自定義控制元件
public void showUpDownView() {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) upAndDownView
.getLayoutParams();
upAndDownView.setDefaultShowHeight(Constant.POI_DEFAULT_SHOW_HEIGHT_BOTTOM);
params.height = Utils.dp2Px(MovieInfoActivity.this, Constant.POI_DEFAULT_SHOW_HEIGHT_BOTTOM);
upAndDownView.setLayoutParams(params);
upAndDownView.setVisibility(View.VISIBLE);
}
public void hideUpDownView() {
upAndDownView.setVisibility(View.GONE);
}
//6佈局
<com.bw.movie.ui.activity.UpAndDownView
android:id="@+id/up_down_view"
android:visibility="gone"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.bw.movie.ui.activity.UpAndDownView>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/title">
<TextView
android:id="@+id/bottom_tv_title"
android:layout_width="90dp"
android:layout_height="35dp"
android:layout_centerHorizontal="true"
android:background="@drawable/tuoyuan_shape4"
android:gravity="center"
android:text="選擇影院"
android:textColor="@color/white"
android:textSize="16sp" />
</RelativeLayout>
<com.bw.movie.view.InnerScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/title"
android:background="@color/floralwhite"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/bottom_tv_date"
android:layout_width="match_parent"
android:layout_height="35dp"
android:background="@color/BulueTheme"
android:gravity="center"
android:text="今天(8月23日)"
android:textColor="@color/white" />
<android.support.v7.widget.RecyclerView
android:id="@+id/bottom_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</com.bw.movie.view.InnerScrollView>
</RelativeLayout>