如何创建兼容的 Foreground drawable selector,实现layout点击效果

Posted on 2016-01-23 by Shawn Wang

Posted in android

Material Design发布后, 越来越多的开发者将其设计规范融入到APP中。 ripple 的涟漪点击效果就是其中之一, 有一些控件已经支持了 ripple 属性,怎么样将 ripple 效果放到 layout 上并且实现不同API Level的兼容呢? 网上有一些实现,如 RippleEffect,兼容性非常不错, 但其 ripple 效果会直接改变前景色,例如在其中放置一个 TextView 元素, 点击 ripple 效果同样会遮蔽文字的颜色。 那么有没有不需要使用自定义控件,实现兼容的点击效果呢? 答案是:有的。

接下来我们使用 View 的 foreground 属性来实现兼容的界面元素点击效果。因为 forground 属性是 View 类的一个属性, 所以此方案可以用于任意派生自 View 的界面控件。 我们要实现的效果如下:

实现效果

API Level 21 (Android L 5.0) 及以上的效果 demo

API Level 21 以下的效果 demo

代码实现

简单来讲,Foreground 定义了绘制于当前内容之上的 Drawable,类似一层覆盖物。所以我们可以为设置 Foreground 的值为 drawable或者color, 那如果将 Froeground 设置为 drawable selector,自然就可以为控件实现点击响应效果了。 比较奇怪的是在 sdk 23 以前,foregrond 属性只对 Framelayout 生效,但这个问题现在得到了解决,所以也请确保你的 compileSdkVersion 大于等于23

因为 ripple 在仅在API Level 21及以上支持,先分别在 drawable-v21 和 drawable 目录创建 selector_foreground.xml 文件。

/res/drawable-v21/selector_foreground.xml

<?xml version="1.0" encoding="utf-8"?>
<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="#15000000" >
</ripple>

res/drawable/selector_foreground.xml

<?xml version="1.0" encoding="utf-8"?>
<selector 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:enterFadeDuration="@android:integer/config_shortAnimTime" 
    android:exitFadeDuration="@android:integer/config_mediumAnimTime">

    <item android:drawable="@drawable/list_pressed_neutral" android:state_pressed="true">
        <shape
            android:shape="rectangle">
            <solid android:color="#15000000" />
        </shape>
    </item>

    <item android:drawable="@android:color/transparent" />

</selector>

接下来我们将在布局中使用 slector_foreground.xml,

res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.v7.widget.CardView
                android:id="@+id/card_1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/margin_card_side"
                android:layout_marginRight="@dimen/margin_card_side"
                android:foreground="@drawable/selector_foreground"
                app:cardBackgroundColor="@android:color/white">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="16dp"
                    android:layout_gravity="center"
                    android:text="This is a CardView"
                    style="@style/LightLarge"/>

            </android.support.v7.widget.CardView>

            <LinearLayout
                android:id="@+id/layout_1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:layout_marginLeft="@dimen/margin_card_side"
                android:layout_marginRight="@dimen/margin_card_side"
                android:background="@android:color/white"
                android:gravity="center"
                android:elevation="2dp"
                android:foreground="@drawable/selector_foreground"
                android:orientation="horizontal">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="16dp"
                    android:text="This is a LinearLayout"
                    style="@style/LightLarge"/>

            </LinearLayout>

            <FrameLayout
                android:id="@+id/layout_2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:layout_marginLeft="@dimen/margin_card_side"
                android:layout_marginRight="@dimen/margin_card_side"
                android:background="@android:color/white"
                android:elevation="2dp"
                android:foreground="@drawable/selector_foreground"
                android:orientation="horizontal">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="16dp"
                    android:layout_gravity="center"
                    android:text="This is a FrameLayout"
                    style="@style/LightLarge"/>

            </FrameLayout>

            <FrameLayout
                android:id="@+id/layout_3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/margin_card_side"
                android:layout_marginRight="@dimen/margin_card_side"
                android:layout_marginTop="16dp"
                android:foreground="@drawable/selector_foreground">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="16dp"
                    android:layout_gravity="center"
                    android:text="A TextView wrapped by FrameLayout"
                    style="@style/LightLarge"/>

            </FrameLayout>

        </LinearLayout>

    </ScrollView>

</FrameLayout>

如上所示,只需要设置相应的 layout foreground 属性为为 @drawable/selector_foreground 即可,类似 layout_3 我们 也可以使用 layout 将其他类型的界面控件包围起来,扩大点击效果的范围。

现在,还差最关键的一步,我们需要为这些设置了 foreground 属性的控件添加 onClick 事件,否则控件不会响应点击操作,自然也不会进入不同的 selector 状态。 如下:

/src/main/java/package/MainActivity.java

package com.effmx.testandroid;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.card_1).setOnClickListener(this);
        findViewById(R.id.layout_1).setOnClickListener(this);
        findViewById(R.id.layout_2).setOnClickListener(this);
        findViewById(R.id.layout_3).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Log.i(TAG, "click on view id:" + v.getId());
    }
}

到此为止,就能实现以上的效果,快自己动手试试吧。


android forground 点击 ripple

Donation

Latest Posts

在 VPS 上搭建 Cisco IPsec|L2TP over IPsec 的极简攻略

三年前我写过一篇在VPS上搭建PPTP VPN的极简攻略, 不过一年前我就不再使用 PPTP VPN 了,最主要的原因是因为 macOS 完全不支持 PPTP;另一个原因是基于 ipsec 协议的 VPN 更加安全,IPsec 协议会加密你的网络数据, 避免泄漏或者中间人攻击。所以现在对于需要全局代

为什么应该使用本地广播(LocalBroadcastManager)

从 Android 诞生已来,就一直有所谓的四大组件,BroadcastReceiver 是其中之一。 几乎在各种样的应用中都有 BroadcastReceiver 的使用,它被应用于接收系统发送的消息以及与其他应用之间的交互,但也被大量的误用于应用内部通信。 然而在同应用中使用则违背 Broadc

推荐 Vocabulary.com

阅读之前如果你还在思考背单词的意义,我建议你先想清楚,或者参考别人的意见,例如知乎的讨论 背单词是必须吗 等问题。 从英语方面来说,我肯定不是大神,小神都算不上。 我背单词的路径基本是 中学大学英语书附录 -> 高频词汇书 -> 扇贝单词 -> Vocabulary.com。 那为什么要来推荐 Vo

Comments