为了有更好的UI体验,一般我们会把button、textview等控件的背景设置上阴影。传统的做法是美工提供一张具有阴影效果的nine patch图,然后将其在xml文件中添加到background属性。这种做法没有问题,不过缺乏灵活性。

图1.使用代码生成的具有“阴影”效果的控件

在android中,每一种在xml文件中定义的图片,均可以使用java代码生成,其中LayerDrawable对应的xml文件的根元素为<layer-list>。

首先我介绍一下使用xml文件生成“阴影”背景效果图片:

<?xml version= "1.0" encoding ="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item >
<shape android:shape="rectangle" >
<solid android:color="#ffbbbbbb" /> <corners android:radius="2dp" /> </shape>
</item > <item
android:bottom="1px"
android:right="1px" >
<shape android:shape="rectangle" >
<solid android:color="#ffdddddd" /> <corners android:radius="2dp" /> <padding
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
</shape>
</item > </layer-list>

解析:

1.shape元素生成ShapeDrawable对象,不过需要注意的是,xml中虽然指明生成"rectangle"类型的对象,但如果想要在java中生成的rectangle具有圆角,那么java中对应的shape应该是RoundRectShape。
2.solid元素指明背景颜色,且paint的style为fill。
3.第二个元素android:bottom等代表的是LayerDrawable中第二个drawable相对于第一个drawable的inset,对应的java代码为:
layerDrawable.setLayerInset(1, 0, 0, 1, 1);
源码为:
 /** Specify modifiers to the bounds for the drawable[index].
left += l
top += t;
right -= r;
bottom -= b;
*/
public void setLayerInset(int index, int l, int t, int r, int b) {
ChildDrawable childDrawable = mLayerState.mChildren[index];
childDrawable.mInsetL = l;
childDrawable.mInsetT = t;
childDrawable.mInsetR = r;
childDrawable.mInsetB = b;
}

可以看出setLayerInset()函数的作用就是将某层(层数从0开始计数)相对于上一层进行向里偏移。当然如果传入的数值为负数,就是向外偏移了,不过这时上层就遮挡住下层了,失去了使用layer的意义了。

4.padding的作用同样非常重要:

(1)当在最上层使用padding时,它指明的是最上层的drawable边缘与内容之间的padding;

(2)当在非最上层使用padding时,它指明当前层与上层之间的padding。

下面使用java代码生成LayerDrawable。

 private void setLayerBg(View view){

         int radius0 = 10;
float[] outerR = new float[] { radius0, radius0, radius0, radius0, radius0, radius0, radius0, radius0 };
RoundRectShape roundRectShape0 = new RoundRectShape(outerR, null, null); int radius1 = 10;
float[] outerR1 = new float[] { radius1, radius1, radius1, radius1, radius1, radius1, radius1, radius1 };
RoundRectShape roundRectShape1 = new RoundRectShape(outerR1, null, null); ShapeDrawable shapeDrawableBg = new ShapeDrawable(); shapeDrawableBg.setPadding(0, 0, 0, 0);
shapeDrawableBg.setShape(roundRectShape0); shapeDrawableBg.getPaint().setStyle(Paint.Style.FILL);
shapeDrawableBg.getPaint().setColor(0xffbbbbbb); ShapeDrawable shapeDrawableFg = new ShapeDrawable(); shapeDrawableFg.setPadding(23, 23, 23, 23);
shapeDrawableFg.setShape(roundRectShape1); shapeDrawableFg.getPaint().setStyle(Paint.Style.FILL);
shapeDrawableFg.getPaint().setColor(0xffdddddd); Drawable[] layers = {shapeDrawableBg, shapeDrawableFg};
LayerDrawable layerDrawable = new LayerDrawable(layers);
layerDrawable.setLayerInset(1, 0, 0, 1, 1); view.setBackgroundDrawable(layerDrawable); }

注释我就不写了,具体的解释见上面的解析。

LayerDrawable和StateListDrawable相结合使用

当我们遇到可点击的控件时,需要给此控件自定义几个不同状态的background,比如按下效果、普通状态效果,这时就需要用到StateListDrawable。

采用LayerDrawable生成的图片具有的只是静态属性,当将不同状态的LayerDrawable添加到一个StateListDrawable中,这样控件不同状态时均选择是否具有阴影效果。

代码如下:

 @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); text = (TextView)this.findViewById(R.id.text);
text.setBackgroundDrawable(getStateListDrawable());
text.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "ttt", Toast.LENGTH_SHORT).show();
}
});
} private Drawable getStateListDrawable(){ StateListDrawable stateListDrawable = new StateListDrawable(); int[] stateHighlighted = new int[]{android.R.attr.state_pressed};
Drawable highlightedDrawable = getLayerDrawable(0xffcccccc);
stateListDrawable.addState(stateHighlighted, highlightedDrawable); int[] stateNormal = new int[]{};
Drawable normalDrawable = getLayerDrawable(0xffdddddd);
stateListDrawable.addState(stateNormal, normalDrawable); return stateListDrawable;
} private Drawable getLayerDrawable(int foregroundColor){ int radius0 = 10;
float[] outerR = new float[] { radius0, radius0, radius0, radius0, radius0, radius0, radius0, radius0 };
RoundRectShape roundRectShape0 = new RoundRectShape(outerR, null, null); int radius1 = 10;
float[] outerR1 = new float[] { radius1, radius1, radius1, radius1, radius1, radius1, radius1, radius1 };
RoundRectShape roundRectShape1 = new RoundRectShape(outerR1, null, null); ShapeDrawable shapeDrawableBg = new ShapeDrawable();
shapeDrawableBg.setPadding(0, 0, 0, 0);
shapeDrawableBg.setShape(roundRectShape0);
shapeDrawableBg.getPaint().setStyle(Paint.Style.FILL);
shapeDrawableBg.getPaint().setColor(0xffbbbbbb); ShapeDrawable shapeDrawableFg = new ShapeDrawable();
shapeDrawableFg.setPadding(23, 23, 23, 23);
shapeDrawableFg.setShape(roundRectShape1);
shapeDrawableFg.getPaint().setStyle(Paint.Style.FILL);
shapeDrawableFg.getPaint().setColor(foregroundColor); Drawable[] layers = {shapeDrawableBg, shapeDrawableFg};
LayerDrawable layerDrawable = new LayerDrawable(layers);
layerDrawable.setLayerInset(1, 0, 0, 1, 1); return layerDrawable;
}

需要注意的是:当给View、TextView、ImageView、ViewGroup等类型的默认没有按下事件的控件添加StateListDrawable时,控件需要设置上click事件,否则按下效果不起作用

备注:

这里只是通过两幅颜色单一的drawable错位简单的生成“阴影效果”,后续可以通过shader等效果,生成逐渐淡出的“阴影”效果。

使用xml定义layer-list的示例:

使用layer-list定义的xml作为button的背景。其中:

Button1:
1.底部drawable没有设置padding
2.顶部drawable没有设置padding
3.顶部drawable设置inset为5px

        android:left="5px"
android:top="5px"
android:bottom="5px"
android:right="5px"

button1背景的完整xml:

<?xml version= "1.0" encoding ="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item >
<shape android:shape="rectangle" >
<solid android:color="#ff00ff00" /> <corners android:radius="3dp" /> </shape>
</item > <item
android:left="5px"
android:top="5px"
android:bottom="5px"
android:right="5px" >
<shape android:shape="rectangle" >
<solid android:color="#ffff0000" /> <corners android:radius="3dp" /> </shape>
</item > </layer-list>

Button2:
1.底部drawable没有设置padding
2.顶部drawable设置inset均为5px

        android:left="5px"
android:top="5px"
android:bottom="5px"
android:right="5px"

3.顶部drawable设置padding均为50dp

     <padding
android:bottom="50dp"
android:left="50dp"
android:right="50dp"
android:top="50dp" />

button2背景的完整xml:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Z-Order 底部drawable -->
<item >
<shape android:shape="rectangle" >
<solid android:color="#ff00ff00" /> <corners android:radius="3dp" /> </shape>
</item > <!-- 顶部drawable -->
<item
android:left="5px"
android:top="5px"
android:bottom="5px"
android:right="5px" >
<shape android:shape="rectangle" >
<solid android:color="#ffff0000" /> <corners android:radius="3dp" /> <padding
android:bottom="50dp"
android:left="50dp"
android:right="50dp"
android:top="50dp" />
</shape>
</item > </layer-list>

Button3:
1.底部drawable设置padding均为20dp
2.顶部drawable没有设置inset
3.顶部drawable设置padding均为50dp

    <padding
android:bottom="50dp"
android:left="50dp"
android:right="50dp"
android:top="50dp" />

button3背景的完整xml:

<?xml version= "1.0" encoding ="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <item>
<shape android:shape="rectangle" >
<solid android:color="#ff00ff00" /> <corners android:radius="3dp" /> <padding
android:bottom="20dp"
android:left="20dp"
android:right="20dp"
android:top="20dp" />
</shape>
</item> <item>
<shape android:shape="rectangle" >
<solid android:color="#ffff0000" /> <corners android:radius="3dp" /> <padding
android:bottom="50dp"
android:left="50dp"
android:right="50dp"
android:top="50dp" />
</shape>
</item> </layer-list>
												

[原创]android使用代码生成LayerDrawable的方法和注意事项的更多相关文章

  1. 安装Android studio出现'tools.jar' seems to be not in Android Studio classpath......的解决方法

    安装Android studio出现'tools.jar' seems to be not in Android Studio classpath......的解决方法 原创 2015年07月31日 ...

  2. [原创]Android Monkey测试工具使用介绍

    [原创]Android Monkey测试工具使用介绍 1 Android Monkey介绍 Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中.它向系统发送伪随机的用户事件 ...

  3. Android C代码回调java方法

    本文将讲述下列三种C代码回调java方法 1.c代码回调java空方法 2.c代码回调java int类型参数方法 3.c代码回调javaString类型参数方法 方法都差不多,先看c代码回调java ...

  4. android MediaPlayer API大全已经方法详解(转载)

    通过这张图,我们可以知道一个MediaPlayer对象有以下的状态: 1)当一个MediaPlayer对象被刚刚用new操作符创建或是调用了reset()方法后,它就处于Idle状态.当调用了rele ...

  5. android中的提示信息显示方法(toast应用)

    android中的提示信息显示方法(toast应用) (2011-10-17 11:02:06) 转载▼ 标签: android toast 杂谈 分类: Android android中toast的 ...

  6. 【转】Android中JNI的使用方法

    Android中JNI的使用方法 首先看一下Android平台的框架图:(网上盗用) 可以看到Android上层的Application和ApplicationFramework都是使用Java编写, ...

  7. [Android设计模式]Android退出应用程序终极方法

    如何干净彻底地退出Android应用程序,是很多开发者的心头痒.如何干净地关闭所有已打开的Activity? 如何关闭指定的Activity? 如何关闭一类Activity? 这里,我们提出一种通过实 ...

  8. 我的Android最佳实践之—— Android启动画面的实现方法

    本文实例讲述了Android启动画面的实现方法.分享给大家供大家参考.具体分析如下: 在应用程序中经常用到启动画面,会启动一个后台线程为主程序的运行准备资源.Android要实现启动画面可以这样做: ...

  9. Chrome模拟手机浏览器(iOS/Android)的三种方法,亲测无误!

    大网站都有推出自己的手机访问版本页面,不管是新闻类还是视频网站,我们在电脑是无法直接访问到手机网站的,比如我经常访问一个3g.qq.com这个手机站点,如果在电脑上直接打开它,则会跳转到其它页面,一般 ...

随机推荐

  1. NSString几个函数

    第一.判断是否包含前缀于后缀 NSString *str=@"hello andy yang"; if([str hasPrefix:@"hello"]) { ...

  2. CSS的四种引入方式

    1.使用link标签引入css文件: <head> <link rel="stylesheet" type="text/css" href=& ...

  3. SQLite主键自增需要设置为integer PRIMARY KEY

    按照正常的SQL语句,创建一个数据表,并设置主键是这样的语句: ), EventType )) 但使用这种办法,在SQLite中创建的的数据表,如果使用Insert语句插入记录,如下语句: INSER ...

  4. 大熊君说说JS与设计模式之(门面模式Facade)迪米特法则的救赎篇------(监狱的故事)

    一,总体概要 1,笔者浅谈 说起“门面”这个设计模式其实不论新老程序猿都是在无意中就已经运用到此模式了,就像我们美丽的JS程序员一样不经意就使用了闭包处理问题, function Employee(n ...

  5. 设计模式之美:Structural Patterns(结构型模式)

    结构型模式涉及到如何组合类和对象以获得更大的结构. 结构型类模式采用继承机制来组合接口实现. 结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法. 因为 ...

  6. C++11 并发指南五(std::condition_variable 详解)

    前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread,std::mut ...

  7. python mysql desc

    #!/usr/bin/python import MySQLdb try: conn=MySQLdb.connect(host='localhost',user='root',passwd='your ...

  8. java继承8个题

    1.实现如下类之间的继承关系,并编写Music类来测试这些类. public class Instrument { public void play(){ System.out.println(&qu ...

  9. MYSQL子查询与连接

    37:子查询与连接SET 列名 gbk;//改变客户端数据表的编码类型. 子查询子查询(Subquery)是指出现在其他SQL语句内的SELECT子句例如SELECT * FROM t1 WHERE ...

  10. forword/ sendRediect

    res.sendRedirect(),是重定向,相当于两次请求,两次相应,地址栏会发生变化. 在实际使用中,重定向不能传指.也就是在requset中储存的值在跳转到另外一个页面后,在目标页面提取不出来 ...