Android动态切换多个View的可编辑/不可编辑模式
Posted carbs_wang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android动态切换多个View的可编辑/不可编辑模式相关的知识,希望对你有一定的参考价值。
前言
在android开发中,经常遇到这样的需求:使一组view动态的在“可编辑”/“不可编辑”两种状态中相互切换,多用于Fragment或者一组View的复用。如何快速实现这样的需求呢?本文介绍一种非常简单的实现方法。
背景
有这样一个布局文件:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
代码省略...
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/container_view "
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 可点击/可编辑的控件 -->
<View ...>
<View ...>
<!-- ①可在此处添加coverView -->
</RelativeLayout>
</ScrollView>
</FrameLayout>
需求是动态的切换container_view中所包含的View的状态,使其同时均可以编辑,或者均不可以编辑。
实现方法
常规思路
首先想到的是,在ScrollView中遍历所有可以编辑/点击的控件,如果处于不可编辑模式,就将其设置为disable。但是这种做法有两处弊端:
1.麻烦,需要遍历逐个设置控件属性;
2.设置disable后,与之前的非disable状态的显示效果有区别;
简洁方法
一种比较简便的实现方法是:
在上述布局的最后添加一个可以屏蔽点击事件的view,并使其宽高等于父控件RelativeLayout(称之为containerView)的宽高。
一开始我的思路是直接加一个view(称之为coverView),并使其属性
android:clickable="false"
并在布局显示出来后,在view.post(Runnable runnable)的runnable中获取container的宽高,并将后来发现这种做法无法实现“覆盖屏蔽下层点击事件”。阅读view源码发现对clickable属性的理解有误,View的onTouchEvent函数如下:
public boolean onTouchEvent(MotionEvent event)
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED)
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0)
setPressed(false);
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
if (mTouchDelegate != null)
if (mTouchDelegate.onTouchEvent(event))
return true;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))
switch (event.getAction())
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed)
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused())
focusTaken = requestFocus();
if (prepressed)
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
if (!mHasPerformedLongPress)
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken)
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null)
mPerformClick = new PerformClick();
if (!post(mPerformClick))
performClick();
if (mUnsetPressedState == null)
mUnsetPressedState = new UnsetPressedState();
if (prepressed)
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
else if (!post(mUnsetPressedState))
// If the post failed, unpress right now
mUnsetPressedState.run();
removeTapCallback();
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event))
break;
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer)
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null)
mPendingCheckForTap = new CheckForTap();
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
else
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop))
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0)
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
break;
return true;
return false;
可以看到有这样一行代码:
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))
//省略代码...
return true
即:当此view是clickable时,touch事件将不会继续传递,这才是我想要的结果。
如下所示布局:
<View
android:id="@+id/cover_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#00000000"
android:clickable="true"/>
将此view添加到containerView中(在本例中添加到上述布局文件的①处),并在view显示时使用post(Runnable r)函数根据containerView的宽高重新设置cover_view的宽高,java代码如下:
private void setCoverIfNotEditable(View containerView,View coverView, boolean editable)
if(editable)
coverView.setVisibility(View.GONE);
else
containerView.post(new Runnable()
@Override
public void run()
ViewGroup.LayoutParams params = coverView.getLayoutParams();
params.width = containerView.getMeasuredWidth();
params.height = containerView.getMeasuredHeight();
coverView.setLayoutParams(params);
coverView.setVisibility(View.VISIBLE);
);
功能完成。
另一种类似解决方案:自定义view
既然最核心的是屏蔽触摸事件向下传递,那么可以自定义view,将其onTouchEvent事件返回true。代码如下:
/**
* created by Carbs.Wang
*/
public class CoverView extends View
public CoverView(Context context)
super(context);
public CoverView(Context context, AttributeSet attrs)
super(context, attrs);
public CoverView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
@Override
public boolean onTouchEvent(MotionEvent event)
return true;
然后在布局中使用此view作为coverView,替换上述的clickable=”true”的coverView即可
<cn.carbs.android.CoverView
android:id="@+id/cover"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#00000000"/>
总结
总结一下实现的方法以及注意事项:
1. containerView必须具有这样的属性:其某一个子view的变大变小,可以不影响其他子view的位置。如RelativeLayout或者FrameLayout。LinearLayout不行。
2. coverView初始设置为不可见或者0dp,并在页面view解析出来后,获取containerView的宽高,推荐使用view.post()这种方式获取view宽高,并将宽高设置到coverView中。
3. coverView在显示时,应当屏蔽触摸事件继续传递,如果不屏蔽,则Z-order底部的view仍然可以获取消息,无法实现需求。
4. 屏蔽事件继续传递有两种方式:一是为View添加clickable=”true”的属性;二是自定义View,使其onTouchEvent返回true,即自己消费了触摸事件,不会继续传递此事件。
以上是关于Android动态切换多个View的可编辑/不可编辑模式的主要内容,如果未能解决你的问题,请参考以下文章
EditView不可编辑状态和可编辑状态动态切换 及 EditView的其它特效
Android 逆向arm 汇编 ( 使用 IDA 解析 arm 架构的动态库文件 | 使用 IDA 打开 arm 动态库文件 | 切换 IDA 中汇编代码显示样式 )
Android 逆向arm 汇编 ( 使用 IDA 解析 arm 架构的动态库文件 | 使用 IDA 打开 arm 动态库文件 | 切换 IDA 中汇编代码显示样式 )