我正在尝试创建一个可以从我的应用程序的底部边缘拉出的菜单,我通过创建一个覆盖RelativeLayout的自定义视图来做到这一点,这是我膨胀的relativelayout的xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/arrow"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerHorizontal="true"
android:src="@drawable/up_arrow" />
<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="60dp"
android:background="#77F5F1" >
</FrameLayout>
还有自定义布局的代码:
public class BottomMenuStack extends RelativeLayout {
private static final int ACCEPTABLE_TOUCH_OFFSET = 70;
private boolean firstTime = true;
private int closedY;
private View arrowView;
private boolean arrowShowing = true;
private boolean moving = false;
private View contentView;
private View mainView;
private boolean contentShowing = false;
private ObjectAnimator showArrowAnimation, hideArrowAnimation, showAnimation, hideAnimation;
private GestureDetector gestureDetector;
private float openedY, lastDistance;
public BottomMenuStack(Context context) {
super(context);
init(context);
}
public BottomMenuStack(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.bottom_menu, this, true);
arrowView = findViewById(R.id.arrow);
contentView = findViewById(R.id.content);
mainView = (View) arrowView.getParent();
// Creates a DestureDetector, to handle the scroll, fling and down events
gestureDetector = new GestureDetector(getContext(), new gestureListener());
gestureDetector.setIsLongpressEnabled(false);
// Initialize the animations
hideArrowAnimation = ObjectAnimator.ofInt(contentView, "top", 0).setDuration(500);
showArrowAnimation = ObjectAnimator.ofInt(contentView, "top", 0).setDuration(500);
hideAnimation = ObjectAnimator.ofFloat(mainView, "translationY", 0).setDuration(500);
showAnimation = ObjectAnimator.ofFloat(mainView, "translationY", 0).setDuration(500);
showAnimation.addListener(new AnimatorListener() {
public void onAnimationStart(Animator animation) {
}
public void onAnimationEnd(Animator animation) {
hideArrow();
}
public void onAnimationCancel(Animator animation) {
}
public void onAnimationRepeat(Animator animation) {
}
});
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (firstTime) {
firstTime = false;
ViewGroup.LayoutParams params = mainView.getLayoutParams();
params.height = 2 * (((View) mainView.getParent()).getHeight() / 3);
mainView.setLayoutParams(params);
closedY = mainView.getHeight() - arrowView.getHeight();
openedY = closedY / 3;
mainView.setTranslationY(closedY);
postInvalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean gestureDetectorRes = gestureDetector.onTouchEvent(event);
if (moving && (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP)) {
if (lastDistance < 0) {
hideContent();
} else {
showContent();
}
moving = false;
return true;
}
return gestureDetectorRes;
}
public boolean isTouchAcceptable(float x, float y) {
if (arrowShowing) {
if (x > arrowView.getLeft() && x < arrowView.getRight())
if (y > arrowView.getTop() + mainView.getY() && y < (arrowView.getTop() + mainView.getY() + arrowView.getHeight()))
return true;
} else {
if (y > (mainView.getY() - ACCEPTABLE_TOUCH_OFFSET) && y < (mainView.getY() + ACCEPTABLE_TOUCH_OFFSET))
return true;
}
return false;
}
public void toggleContent() {
if (contentShowing)
hideContent();
else
showContent();
}
public void hideContent() {
if (contentShowing) {
cancelScrollAnimations();
hideAnimation.setFloatValues(mainView.getY(), closedY);
hideAnimation.start();
contentShowing = false;
}
}
public void showContent() {
if (!contentShowing) {
cancelScrollAnimations();
showAnimation.setFloatValues(mainView.getTranslationY(), openedY);
showAnimation.start();
contentShowing = true;
} else if (!showAnimation.isRunning()) {
hideArrow();
}
}
public void toggleArrow() {
if (arrowShowing)
hideArrow();
else
showArrow();
}
public void showArrow() {
if (!arrowShowing) {
cancelArrowAnimations();
showArrowAnimation.setIntValues(contentView.getTop(), arrowView.getHeight());
showArrowAnimation.start();
arrowShowing = true;
}
}
public void hideArrow() {
if (arrowShowing) {
cancelArrowAnimations();
hideArrowAnimation.setIntValues(contentView.getTop(), 0);
hideArrowAnimation.start();
arrowShowing = false;
}
}
private void cancelScrollAnimations() {
if (showAnimation != null)
showAnimation.cancel();
if (hideAnimation != null)
hideAnimation.cancel();
}
private void cancelArrowAnimations() {
if (showArrowAnimation != null)
showArrowAnimation.cancel();
if (hideArrowAnimation != null)
hideArrowAnimation.cancel();
}
private class gestureListener implements OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
if (isTouchAcceptable(e.getX(), e.getY())) {
showArrow();
moving = true;
return true;
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (moving) {
if (velocityY > 0)
hideContent();
else
showContent();
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float y = e2.getY();
if (moving) {
lastDistance = distanceY;
if (y < closedY && y > openedY)
mainView.setTranslationY(y);
}
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
}
}
问题是,每当我尝试在层次结构查看器上调试此布局时(在模拟器上运行应用程序时),我看到的视图位置与它们在屏幕上实际看到的位置完全不同,这里有一些屏幕截图可以更好地解释这种情况:
Hierarchy Viewer screenshot
Emulator screenshot
在这些屏幕截图中可以看到箭头,即在模拟器的截图中出现在屏幕底部边框的上方,根据 Hierarchy Viewer 是在上边距的正下方,这怎么可能?我在移动视图时做错了什么?
触发动画,使视图在模拟器屏幕上移动,不会改变 Hierarchy Viewer 预览。