我有一个使用Android Activity
的{{1}}。
当仅使用NavigationDrawer
s(像往常一样)时,一切都很完美。
但现在我想在我的应用程序的其他活动中使用此fragment
,对于其中一些活动,
我不希望主视图为drawer
。
问题
问题是,fragment
本身的onTouchEvent()
(以及孩子的activity
} onItemClickedListener()
未被调用,因为ListView
消耗它。
当然,我希望它被称为:)
毋庸置疑,我希望答案很简单(甚至是XML),希望不要扩展drawer
类(除非那是当然的)。
更多信息
Activity的主要布局非常简单,基本上是Drawer
,ListView
位于其顶部(在XML下面)。
DrawerLayout
有一个Drawer
,因为它是fragment
(用于片段导航),当然还有childView
项ListView
。
我已经看到很多关于(不完全)类似问题的问题,而且经常回答是在Drawer
和父视图({1}}上使用onInterceptTouch()
,requestDisallowInterceptTouchEvent()
({ {1}}的主要内容)甚至DrawerLayout
(返回False)在抽屉的Activity
上。
似乎没有什么可以做到的。
I read this link
似乎在某处使用拦截方法似乎就是答案。但是怎么样?
如果您需要任何代码,请与我们联系。但这是一个非常基本的代码/布局。
谢谢!
答案 0 :(得分:15)
显然答案有点简单,虽然它确实会让你扩展DrawerLayout并做一些思考,也许会导致一些奇怪的结果(使用LAST 例如,我还没有看到任何,但)。
无论如何,向后看的相关问题有助于理解问题(稍后会解释第一个问题):
1. DrawerLayout prevents call of MainActivity.onTouchEvent()
2. How can I requestDisallowTouchEvents on Android DrawerLayout
3. Set drag margin for Android Navigation Drawer
<强>答案强>
首先,请注意我在这里举了很多例子。如果你只想要最好的(对我而言),请跳到最后一个
其次,如果某人有足够的声誉,请对第一个链接的问题发表评论并给出这个答案的链接(它可以帮助那个人)。
示例1
好吧,基本上,只需扩展Android的DrawerLayout并将onTouchEvent()替换为:
@Override
public boolean onTouchEvent(MotionEvent arg0) {
super.onTouchEvent(arg0);
return false;
}
此解决方案将执行任何操作,但不会在幻灯片上打开抽屉,只会打开菜单点击等。此外,它会在抽屉打开时转发点击次数 例如,触摸它之外不会关闭它,而是点击后面的任何内容(例如ListView)。不要再努力......
示例2
现在,让我们捕捉开放的OR可见情况,返回true(并在Drawer处消耗动作)。
@Override
public boolean onTouchEvent(MotionEvent arg0) {
super.onTouchEvent(arg0);
if(isDrawerOpen(findViewById(R.id.list_slidermenu)) ||
isDrawerVisible(findViewById(R.id.list_slidermenu))){
return true;
}
return false;
}
此解决方案更好,因为当抽屉打开或甚至可见时(滑动开始...),它可以防止抽屉后面的咔嗒声。但触摸滑动它仍然无法正常工作。
示例3
好吧,让我们分开案例吧。在抽屉的边距内触摸(MotionEvent.ACTION_DOWN)(谷歌在触摸时希望滑动抽屉的区域)
将导致返回True以使用该操作,而其他人将转发该事件(返回False)。
@Override
public boolean onTouchEvent(MotionEvent arg0) {
super.onTouchEvent(arg0);
float edge = 30;//that's for a left drawer obviously. Use <parentWidth - 30> for the right one.
View mDrawerListView = findViewById(R.id.drawer_listview);
if(isDrawerOpen(mDrawerListView) ||
isDrawerVisible(mDrawerListView)){
return true;
} else if(arg0.getAction() == MotionEvent.ACTION_DOWN && arg0.getX() > edge){
return false;
}
return true;
}
请注意,我使用了30dp。这就是我发现的边缘(虽然在其中一个链接中据说是20 ....)。
嗯,根据Android的说法,下一个例子当然是决定什么是这个边缘(参见上面的代码)值。我们不想 使用可能改变的数字或其他。
新问题
所以现在第一个链接应该派上用场。它“破解”抽屉代码以获得抽屉边缘/ megin数。但是,它对我不起作用,因为找不到那些确切的字段名称
我运行mDrawerLayout.getClass()。getField()返回所有字段,但没有任何运气找到我们想要的东西。任何人?
最后一个示例 - 完整代码
好的,看看第3个例子,在理解了我做了什么后,我们可以通过扩展onFinishInflate()方法并将其保存为全局变量来加快速度。
这个CustomDrawerLayout供以后使用。我们也可以将第一个“if”放在第二个“if”中以节省更多的工作。好的,这里是:
View mDrawerListView;
...
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDrawerListView = findViewById(R.id.drawer_listview);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if(event.getX() > 30 && event.getAction() == MotionEvent.ACTION_DOWN){
if(isDrawerOpen(mDrawerListView) || isDrawerVisible(mDrawerListView)){
return true;
} else{
return false;
}
}
return true;
}
现在就是这样!希望它能帮助未来的人,呵呵......
答案 1 :(得分:1)
在处理同样的问题时,我受到guy_m's answer的启发,并将他的建议归结为以下解决方案。
同样,它等于扩展DrawerLayout并覆盖onInterceptTouchEvent()。逻辑很简单:
每当触摸事件发生关闭抽屉视图(可滑动部分)时,我们返回false。然后我们的DrawerLayout在处理事件时不在游戏中 - 事件由我们在相应位置将放入 DrawerLayout的任何视图处理。
另一方面,当事件在抽屉视图内发生时,我们委托super.onInterceptTouchEvent()来决定如何处理事件。这样抽屉就会像以前那样在触摸手势上自动滑入和滑出。
以下代码示例适用于DrawerLayout,其抽屉视图位于右侧(android:gravity =“right”)。应该很明显如何修改它以涵盖左侧抽屉的情况。
public class CustomDrawerLayout extends DrawerLayout
{
@Override
public boolean onInterceptTouchEvent( MotionEvent event )
{
final View drawerView = getChildAt( 1 );
final ViewConfiguration config = ViewConfiguration.get( getContext() );
// Calculate the area on the right border of the screen on which
// the DrawerLayout should *always* intercept touch events.
// In case the drawer is closed, we still want the DrawerLayout
// to respond to touch/drag gestures there and reopen the drawer!
final int rightBoundary = getWidth() - 2 * config.getScaledTouchSlop();
// If the drawer is opened and the event happened
// on its surface, or if the event happened on the
// right border of the layout, then we let DrawerLayout
// decide if it wants to intercept (and properly handle)
// the event.
// Otherwise we disallow DrawerLayout to intercept (return false),
// thereby letting its child views handle the event.
return ( isDrawerOpen( drawerView ) && drawerView.getLeft() <= event.getX()
|| rightBoundary <= event.getX() )
&& super.onInterceptTouchEvent( event );
}
}
答案 2 :(得分:1)
有了这些答案,我仍然遇到一些麻烦。我可以将motionEvent恢复到活动状态,但我会按片段或屏幕上的所有内容丢失onClick侦听器的答案。因此,我找到了使一切正常工作的另一种方法(从活动覆盖OntouchEvent时获取答案,并对onClick Listener进行回答)
扩展DrawerLayout并覆盖此方法:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(super.onInterceptTouchEvent(ev)) return true;
else {
Activity activity = AppContext.getCurrentActivity();
return activity.onTouchEvent(ev);
}
}
如果抽屉需要运动事件,请让它处理。如果没有,则将事件自己传递给活动。 (AppContext.getCurrentActivity是您当前活动的东西,例如,您可以将活动作为对薄板布局OnCreate的弱引用)
用这种方法的好处是,您不必关心边缘,也不必关心起始或结束。而且您也不关心它是打开还是关闭。一切正常。
答案 3 :(得分:0)
我有一个解决方案:
在屏幕布局上设置OnTouchListener
(通常是DrawerLayout
的第一个子视图)并将TouchEvent
传输到自定义GestureDetector
。
所以,你可以在里面做自己的事情。另一个重要的事情是:如果您要覆盖onSingleTapUp()
或其他内容,则应return true
onDown()
MotionEvent
以确保您可以让其余onSingleTapUp()
进行private class MyGestureListener implements GestureDetector.OnGestureListener{
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// do your own things
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}
1}}工作。
mGestureDetector=new GestureDetector(this, new MyGestureListener());
layout_content.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});
并设置它:
android:fitSystemWindows="true"
答案 4 :(得分:0)
要添加到guy_m的答案,这是我从右侧打开的抽屉的实现,包括构造函数,以便在布局编辑器中可以查看,并且还考虑用户从边缘点滑过的时间:
public class CustomDrawerLayout extends DrawerLayout {
View mDrawerListView;
float edge;
int holddown = 0;
static final String TAG = CustomDrawerLayout.class.getSimpleName();
public CustomDrawerLayout(@NonNull Context context) {
super(context);
setscreendimensionvals(context);
}
public CustomDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setscreendimensionvals(context);
}
public CustomDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setscreendimensionvals(context);
}
private void setscreendimensionvals(Context context){
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
/*((Activity) context).getWindowManager()
.getDefaultDisplay()
.getMetrics(displayMetrics); */
int width = displayMetrics.widthPixels;
float density = displayMetrics.density;
edge = width - (30 * density); // 30 is the edge of the screen where the navigation drawer comes out
Log.d(TAG,"edge: " + edge);
Log.d(TAG,"width: " + width);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDrawerListView = findViewById(R.id.drawerconstraint_overworld);
}
@Override
public boolean onTouchEvent(MotionEvent event){
super.onTouchEvent(event); // need to add action up and a local variable to detect when lifted finger
//Log.d(TAG,"point: " + event.getX());
if(event.getX() >= edge && (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE)){
holddown = 1;
//Log.d(TAG,"hold down");
}
if(event.getAction() == MotionEvent.ACTION_UP){
holddown = 0;
//Log.d(TAG,"hold up");
}
if(holddown == 1){
return true;
}else{
if(isDrawerOpen(mDrawerListView) || isDrawerVisible(mDrawerListView)){
return true;
} else{
return false;
}
}
}
}
答案 5 :(得分:0)
对于可能不幸遇到这种问题的人来说,我会用我自己的问题案例和解决方案添加到其他人的答案中,希望更少的人会面临这个头疼的噩梦。
应注意的是,我的解释很可能适用于任何可滑动视图其父视图 DrawerLayout
(例如,此解决方案仅适用于 DrawerLayout
的子视图) ,但为了清楚起见,我将重温我的经验和我的辛劳。
就我而言,我需要在 MaterialCalendarView
中有一个 DrawerLayout
(3rd-party CalendarView
on steroids),右侧有一个 NavigationView
(即 "android:gravity"="end"
) .实施视图层次结构后不久,我意识到我的 NavigationView
和 MaterialCalendarView
的滑动事件之间存在冲突。
本质上,当我开始向向右滑动MaterialCalendarView
以便滑动回下个月时,我结束了触发 DrawerLayout
'的触摸事件拦截器并关闭所述 DrawerLayout
而不是滑动到上个月。< /p>
所以,解决方案应该很简单,不是吗?在 onTouchListener
上设置一个 MaterialCalendarView
,调用 requestDisallowInterceptTouchEvent()
,然后就结束了——类似于在视图托管活动中:
calendar.setOnTouchListener { _, motionEvent ->
when(motionEvent.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
drawerLayout.requestDisallowInterceptTouchEvent(true)
}
}
true
}
...你应该准备好了吗?
好吧,我在这里回答的事实足以推断出事实并非如此,而且我的 onTouchListener
没有像其他人一样被触发。< /p>
在广泛搜索此线程并尝试遵循每个人的建议之后,我发现所提供的解决方案对于那些只想“排除”视图不被 {{1} } 的触摸事件拦截器。一些想法完全瘫痪了我的触摸事件基础设施,而其他想法只是给了我更多相同的行为。我遇到了障碍,不知道该怎么办。
然后,顿悟。
我意识到,由于我在编写自定义视图方面缺乏经验,我错过了一个显而易见的事情:我需要做的只是找出 DrawerLayout
的位置,获取其坐标,然后查看是否有任何触摸事件是为了调用正确的实现(无论是 Activity 还是默认的 MaterialCalendarView
一个)!而且,当然,因为在前者中,DrawerLayout
禁用 onTouchListener
对触摸事件的拦截,这意味着只有 DrawerLayout
可以处理滑动!原来如此简单!
然后快速学习 MaterialCalendarView
s,阅读 MotionEvent
到底是什么,以及崩溃之间的泥泞,我终于编写了自定义 Rect
它仅使用 Activity 实现响应我在 DrawerLayout
上的滑动,而忽略了外部的滑动,选择了 MaterialCalendarView
触摸拦截器:
DrawerLayout
这真的不是火箭科学,但我认为值得展示,因为它对我来说是新的和出乎意料的(毫无疑问还有很多其他人还没有在这里冒险)。尽管如此,我相信这种实现可能会忽略嵌入在 class EventCalendarDrawerLayout : DrawerLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
lateinit var calendar: MaterialCalendarView
lateinit var drawer: View
override fun onFinishInflate() {
super.onFinishInflate()
drawer = getChildAt(1)
calendar = findViewById(R.id.event_calendar)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
val rect = Rect()
calendar.getGlobalVisibleRect(rect) // get the calendar rect positions
// respond to proper motions and forward events contained inside the calendar's rect only
if((event.action == MotionEvent.ACTION_MOVE ||
event.action == MotionEvent.ACTION_DOWN) && rect.contains(event.x.roundToInt(), event.y.roundToInt())) {
return (context as Activity).onTouchEvent(event)
}
// otherwise return the default intercept touch event response
return super.onInterceptTouchEvent(event)
}
}
中的尽可能多的视图。