如何处理 9-patch 和 CardView 上的 Ripple 效果,并控制选择器的状态?
Posted
技术标签:
【中文标题】如何处理 9-patch 和 CardView 上的 Ripple 效果,并控制选择器的状态?【英文标题】:How to handle the Ripple effect on 9-patch and CardView, and have control over the states of the selector? 【发布时间】:2015-01-26 01:35:10 【问题描述】:背景
我希望为 android Lollipop 及更高版本的 listView 项目添加一个简单的涟漪效果。
首先我想将它设置为简单的行,然后设置为 9-patch 行,甚至 CardView。
问题
我确信这会非常简单,因为它甚至不需要我定义普通选择器。 即使是简单的行,我也没有这样做。出于某种原因,涟漪效应超出了行的边界:
不仅如此,在某些情况下,项目的背景会卡在我设置的颜色上。
代码
这是我尝试过的:
MainActivity.java
public class MainActivity extends ActionBarActivity
@Override
protected void onCreate(final Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ListView listView = (ListView) findViewById(android.R.id.list);
final LayoutInflater inflater = LayoutInflater.from(this);
listView.setAdapter(new BaseAdapter()
@Override
public View getView(final int position, final View convertView, final ViewGroup parent)
View rootView = convertView;
if (rootView == null)
rootView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
((TextView) rootView.findViewById(android.R.id.text1)).setText("Test");
return rootView;
@Override
public long getItemId(final int position)
return 0;
@Override
public Object getItem(final int position)
return null;
@Override
public int getCount()
return 2;
);
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/list"
android:layout_
android:layout_
android:cacheColorHint="@android:color/transparent"
android:divider="@null"
android:dividerHeight="0px"
android:fadeScrollbars="false"
android:fastScrollEnabled="true"
android:listSelector="@drawable/listview_selector"
android:scrollingCache="false"
android:verticalScrollbarPosition="right" />
res/drawable-v21/listview_selector.xml(我有其他安卓版本的普通选择器)
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" />
我尝试过的
除了上面的简单代码之外,我还尝试为每个项目的背景属性设置选择器,而不是在 ListView 上使用“listSelector”,但没有帮助。
我尝试的另一件事是设置项目的前景,但结果也相同。
问题
我该如何解决这个问题?为什么会发生?我做错了什么?
如何更进一步,支持 9-patch 甚至 CardView ?
另外,如何设置新背景的状态,比如被选中/选中?
更新:视图外的绘图已使用以下方法修复:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight" >
<item android:id="@android:id/mask">
<color android:color="@color/listview_pressed" />
</item>
</ripple>
仍然存在背景卡住的问题,我找不到如何处理其余缺失的功能(9-patch、cardView、...)。
我认为卡住的颜色与将其用作视图的前景有关。
编辑:我看到有些人不明白这里的问题是什么。
这是关于处理新的涟漪效应,同时仍然使用旧的选择器/CardView。
例如,这里有一个 selector-drawble:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="..." android:state_selected="true"/>
<item android:drawable="..." android:state_activated="true"/>
<item android:drawable="..." android:state_focused="true" android:state_pressed="true"/>
<item android:drawable="..." android:state_pressed="true"/>
<item android:drawable="..."/>
</selector>
这可以用作列表选择器或单个视图的背景。
但是,我找不到如何将它与可绘制的波纹一起使用。
我知道涟漪已经处理了一些状态,但对于一些状态,它没有。另外,我不知道如何让它处理 9-patch 和 CardView。
我希望现在更容易理解我遇到的问题。
关于波纹颜色“卡住”的问题,我认为是因为我的布局方式。我想要一个可以检查的布局(当我决定时)并且还具有点击的效果,所以这就是我所做的(基于this website 和另一个我找不到的):
public class CheckableRelativeLayout extends RelativeLayout implements Checkable
private boolean mChecked;
private static final String TAG = CheckableRelativeLayout.class.getCanonicalName();
private static final int[] CHECKED_STATE_SET = android.R.attr.state_checked ;
private Drawable mForegroundDrawable;
public CheckableRelativeLayout(final Context context)
this(context, null, 0);
public CheckableRelativeLayout(final Context context, final AttributeSet attrs)
this(context, attrs, 0);
public CheckableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyle)
super(context, attrs, defStyle);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckableRelativeLayout, defStyle,
0);
setForeground(a.getDrawable(R.styleable.CheckableRelativeLayout_foreground));
a.recycle();
public void setForeground(final Drawable drawable)
this.mForegroundDrawable = drawable;
public Drawable getForeground()
return this.mForegroundDrawable;
@Override
protected int[] onCreateDrawableState(final int extraSpace)
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked())
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
return drawableState;
@Override
protected void drawableStateChanged()
super.drawableStateChanged();
final Drawable drawable = getBackground();
boolean needRedraw = false;
final int[] myDrawableState = getDrawableState();
if (drawable != null)
drawable.setState(myDrawableState);
needRedraw = true;
if (mForegroundDrawable != null)
mForegroundDrawable.setState(myDrawableState);
needRedraw = true;
if (needRedraw)
invalidate();
@Override
protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight)
super.onSizeChanged(width, height, oldwidth, oldheight);
if (mForegroundDrawable != null)
mForegroundDrawable.setBounds(0, 0, width, height);
@Override
protected void dispatchDraw(final Canvas canvas)
super.dispatchDraw(canvas);
if (mForegroundDrawable != null)
mForegroundDrawable.draw(canvas);
@Override
public boolean isChecked()
return mChecked;
@Override
public void setChecked(final boolean checked)
setChecked(checked, true);
public void setChecked(final boolean checked, final boolean alsoRecursively)
mChecked = checked;
refreshDrawableState();
if (alsoRecursively)
ViewUtil.setCheckedRecursively(this, checked);
@Override
public void toggle()
setChecked(!mChecked);
@Override
public Parcelable onSaveInstanceState()
// Force our ancestor class to save its state
final Parcelable superState = super.onSaveInstanceState();
final SavedState savedState = new SavedState(superState);
savedState.checked = isChecked();
return savedState;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void drawableHotspotChanged(final float x, final float y)
super.drawableHotspotChanged(x, y);
if (mForegroundDrawable != null)
mForegroundDrawable.setHotspot(x, y);
@Override
public void onRestoreInstanceState(final Parcelable state)
final SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
setChecked(savedState.checked);
requestLayout();
// /////////////
// SavedState //
// /////////////
private static class SavedState extends BaseSavedState
boolean checked;
SavedState(final Parcelable superState)
super(superState);
private SavedState(final Parcel in)
super(in);
checked = (Boolean) in.readValue(null);
@Override
public void writeToParcel(final Parcel out, final int flags)
super.writeToParcel(out, flags);
out.writeValue(checked);
@Override
public String toString()
return TAG + ".SavedState" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked
+ "";
@SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
@Override
public SavedState createFromParcel(final Parcel in)
return new SavedState(in);
@Override
public SavedState[] newArray(final int size)
return new SavedState[size];
;
编辑:修复是为我所做的布局添加下一行:
@SuppressLint("ClickableViewAccessibility")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onTouchEvent(final MotionEvent e)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && //
e.getActionMasked() == MotionEvent.ACTION_DOWN && //
mForeground != null)
mForeground.setHotspot(e.getX(), e.getY());
return super.onTouchEvent(e);
【问题讨论】:
【参考方案1】:RippleDrawable
扩展 LayerDrawable
。触摸反馈可绘制对象可能包含多个子层,包括未绘制到屏幕上的特殊遮罩层。通过将其android:id
值指定为mask
,可以将单个层设置为掩码。第二层可以是StateListDrawable
。
例如,这是我们的 StateListDrawable
资源,名称为 item_selectable.xml
:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="..." android:state_selected="true"/>
<item android:drawable="..." android:state_activated="true"/>
<item android:drawable="..." android:state_focused="true" android:state_pressed="true"/>
<item android:drawable="..." android:state_pressed="true"/>
<item android:drawable="..."/>
</selector>
为了与选择器一起实现涟漪效果,我们可以将上面的drawable设置为RippleDrawable
层,名称为list_selector_ripple.xml
:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="@android:color/white"/>
</item>
<item android:drawable="@drawable/item_selectable"/>
</ripple>
UPD:
1) 要将此drawable 与CardView
一起使用,只需将其设置为android:foreground
,如下所示:
<android.support.v7.widget.CardView
...
android:foreground="@drawable/list_selector_ripple"
/>
2) 为了使波纹效果在 9-patch 的范围内起作用,我们应该将此 9-patch 可绘制对象设置为波纹可绘制对象的遮罩 (list_selector_ripple_nine_patch.xml
):
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorControlHighlight">
<item android:id="@android:id/mask" android:drawable="@drawable/your_nine_patch" />
<item android:drawable="@drawable/your_nine_patch" />
</ripple>
然后设置视图的背景:
<LinearLayout
...
android:background="@drawable/list_selector_ripple_nine_patch"
/>
【讨论】:
不错。谢谢!波纹会在 9-patch 的范围内起作用吗?还是会进一步扩大视图的整体大小?甚至可以自定义波纹(颜色除外)吗?另外,您还将如何处理 CardView?例如,假设我希望 CardView 是可点击的(使用涟漪效果),并且当我标记它时,它处于“选中”(或“选中”)状态,因此它的内容更改为我选择的颜色。 谢谢!那么遮罩是用来限制波纹的绘制的吗?如果是这样,当您选择不是 9-patch 的颜色或可绘制对象时,它会做什么?关于“前景”,我认为 v5.0 上存在一个错误,它不会将波纹放在您触摸过的地方(意味着它只从中心开始):code.google.com/p/android-developer-preview/issues/…。修好了吗? 遮罩定义了波纹动画的边界。它可以是任何可绘制的(形状、9-patch、颜色等)。我不知道你发布的问题。我有一个带有 Lollipop 的 Nexus 4,它没有这个错误。 波纹效果将出现在不透明像素的排列位置。为蒙版设置什么样的颜色并不重要。如果将遮罩设置为颜色,则波纹效果将被整个View
遮罩。
假设我们在item_selectable.xml
的按下状态下使用带有 alpha 的颜色(例如,#88000000
、android:color/transparent
等)。如果我们没有在list_selector_ripple.xml
中设置遮罩,则涟漪效果会在子层上被遮盖,即从item_selectable.xml
可绘制为按下状态。此可绘制对象是带有 alpha 的颜色,因此波纹效果的边边界为零。要看到这种效果,我们应该手动设置遮罩。如果我们对按下状态(#fff
、android:color/white
等)使用不带 alpha 的颜色,则设置掩码是可选的。【参考方案2】:
创建波纹的简单方法在drawable-v21文件夹中创建一个xml并将此代码用于xml。
android:backgroung="@drawable/ripple_xyz"
如果,通过java/动态使用。
View.setBackgroundResource(R.drawable.ripple_xyz);
这是ripple_xyz.xml。
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#228B22" >
// ^ THIS IS THE COLOR FOR RIPPLE
<item>
<shape
android:shape="rectangle"
android:useLevel="false" >
<solid android:color="#CCFFFFFF" />
// ^ THIS IS THE COLOR FOR BACK GROUND
</shape>
</item>
【讨论】:
这很好,但我已经标记了一个答案(实际上是一个好答案)。但是,我会给你 +1 的努力。【参考方案3】:你必须在波纹中设置一个遮罩层。
类似这样的:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item android:id="@id/mask">
<color android:color="@color/myColor" />
</item>
</ripple>
【讨论】:
掩码 id 无法识别。此外,在放置第二种颜色之后,即使我停止选择一个项目,它的背景似乎也与其他项目不同。另外,我应该如何处理 9-patch 背景以及 CardView ? 你能帮忙吗?我已经设置了 android:id="@android:id/mask" ,但是在某些情况下,当我长按一个项目并继续触摸其他区域,然后停止触摸时,视图的背景会保持填充状态。 . 可能是因为我尝试将可绘制的波纹设置为前景?【参考方案4】:查看本教程。涟漪效应是实施和它的工作正常。 Ripple Effect on RecyclerView
【讨论】:
它只是说要使用 android:background="?android:attr/selectableItemBackground" ,但是除此之外我如何使用选择器,以及在 9-patch 和 CardViews 上?【参考方案5】:您可以通过以下代码处理 9 个补丁图像:
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight" >
<item android:id="@android:id/mask"
android:drawable="@drawable/comment_background">
</item>
</ripple>
comment_background 是 9 个补丁图像
【讨论】:
如何使背景也具有“选中”(或“选中”)状态?我可以以某种方式在里面放一个选择器吗?另外,如何在 CardView 上设置它?以上是关于如何处理 9-patch 和 CardView 上的 Ripple 效果,并控制选择器的状态?的主要内容,如果未能解决你的问题,请参考以下文章
如何处理 UIDeviceOrientationFaceUp 和 UIDeviceOrientationFaceDown?