本项目Demo: https://github.com/liaozhoubei/CustomViewDemo
Android给开发者定义了很多图像样式,但是由于需求不同,想修改其中的视图样式,又或者想做出自己喜欢的样式,这时改怎么办?
这篇文章就是教你怎样修改或者制作这些样式的。
本文有以下几个内容:
1、修改ProgressBar的样式
2、使用动画的菜单视图
3、轮播图的实现
4、显示有下拉框的视图
5、自定义的开关视图
6、下拉刷新和加载更多的实现
7、侧边栏视图的实现
链接地址:
扩展阅读:
Android 精通自定义视图(2) //www.greatytc.com/p/092e126b623f
Android 精通自定义视图(3) //www.greatytc.com/p/1660479e76ef
Android 精通自定义视图(4) //www.greatytc.com/p/850e387fc9d8
Android 精通自定义视图(5) //www.greatytc.com/p/93feac19c396
现在就让我们开启自定义视图之旅吧!
修改ProgressBar样式
在自定义视图之前我们先热热身子,找个简单的事情来转换一下情绪。
Android的ProgressBar的原始样式是一条直线,看上去非常的粗糙,所以我们想将其修改为下面这种样子:
那么我们应该如何着手呢?其实这时我们可以查看Android自身是怎么制定样式的,然后通过学习他们制定样式的方法来修改ProgressBar。
找到我们SDK的安装目录,然后进入platforms目录,随便选择一个Android版本,在这里我选择了android-16,然后进去\data\res\values,找到其中的styles.xml文件,这个就是Android放置系统样式的地方了。
现在我们要查找到ProgressBar的Horizontal样式是如何制定的,直接在styles.xml中搜索,找到了以下代码:
<style name="Widget.ProgressBar.Horizontal">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
<item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
</style>
通过分析,我们发现ProgressBar是通过设定android:progressDrawable来设定样式的,那么其中drawable/progress_horizontal又是什么呢?再次到drawable中搜索到progress_horizontal.XML文件,打开发现:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
···
</item>
<item android:id="@android:id/secondaryProgress">
···
</item>
<item android:id="@android:id/progress">
···
</item>
</layer-list>
这下我们被这些代码晃得眼花缭乱的,这个layer-list是什么东西?
别怕,我们可以直接去andorid的开发者官网查看答案,开发者官网是andorid最大的学习资料,有不懂的地方直接去哪里找就是了。
果然我们找到了layer-list的信息,从它的信息上得知,这个一个管理图片资源的图片对象,它可以按照顺序,将图片一层层的叠加上去。
好了,我们已经明白了layer-list,那么我们怎么使用它呢?别怕,开发者官网也把使用方法给我们了,直接把代码拿下,自己修改一下就好了。
首先在项目的res目录下创建drawable目录,然后新建style_progress.xml,添加一下代码:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@android:id/background">
<bitmap
android:gravity="center"
android:src="@drawable/security_progress_bg" />
</item>
<item android:id="@android:id/secondaryProgress">
<bitmap
android:gravity="center"
android:src="@drawable/security_progress" />
</item>
<item android:id="@android:id/progress">
<bitmap
android:gravity="center"
android:src="@drawable/security_progress" />
</item>
</layer-list>
其中security_progress_bg、security_progress 都是另外的图片资源,在获得了drawable资源之后,就在layout中新建activity_progressbar.xml布局文件,添加一下代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBar1"
android:layout_centerHorizontal="true"
android:layout_marginTop="64dp"
android:onClick="startProgress"
android:text="start Progress" />
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="126dp"
android:max="100"
android:progress="20"
android:progressDrawable="@drawable/style_progress" />
</RelativeLayout>
最后再新建的ProgressBarActivity中调用自定义的ProgressBar:
public class ProgressBarActivity extends Activity {
private ProgressBar progressBar1;
private int progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progressbar);
progressBar1 = (ProgressBar) findViewById(R.id.progressBar1);
progressBar1.setProgress(0);
}
public void startProgress(View v) {
progress = 0;
new Thread() {
public void run() {
for (int i = 0; i <= 100; i++) {
SystemClock.sleep(100);
progress++;
progressBar1.setProgress(progress);
}
};
}.start();
}
}
这时我们的自定义的ProgressBar就完成了!小伙伴们也可以自定义出更酷更炫的ProgressBar样式哦,不管是条状的,还是环形的,都是可以自己定义的。而且Android其他控件的布局也是可以通过查看Android自身的样式来修改。
实现动画自定义菜单
ProgressBar做完之后,我们想实现一个更酷更炫的能动的菜单视图,如下:
在这个视图中,我们点击中间三条横线的菜单时,最外层菜单如果存在就隐藏,不存在就显示;点击最里面的主页按钮时,则是将最外层和中间的菜单隐藏,或者显示中间菜单;当我们点击Menu(虚拟机要设置有实体键)时,就会将所有的菜单隐藏或显示。
那么这个看起来又酷又炫的动画效果是怎么实现的呢?其实其中的核心就是使用RotateAnimation这个API,以及对动画的处理。
我们先上代码:
public class AnimationUtil {
// 数值大小在动画运行时会变化,大于0表示动画开始了,等于或小于0位动画结束了
public static int runningAnimationCount = 0;
public static void RotateAnimationOut(RelativeLayout layout, long delay) {
// 获得layout视图中子控件的个数
int childCount = layout.getChildCount();
for (int i = 0; i < childCount; i ++) {
// 设置layout子控件为不可点击
layout.getChildAt(i).setEnabled(false);
}
// 设置基于自身的选择动画
RotateAnimation rotateAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 1.0f);
rotateAnimation.setDuration(500);
// 设置动画的延时时间
rotateAnimation.setStartOffset(delay);
// 设置动画停留在结束位置
rotateAnimation.setFillAfter(true);
// 设置动画的监听
rotateAnimation.setAnimationListener(new MyAnimationLisenter());
layout.startAnimation(rotateAnimation);
}
public static void RotateAnimationIn(RelativeLayout layout, long delay) {
// 获得layout视图中子控件的个数
int childCount = layout.getChildCount();
for (int i = 0; i < childCount; i ++) {
// 设置layout子控件为不可点击
layout.getChildAt(i).setEnabled(true);
}
RotateAnimation rotateAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 1.0f);
rotateAnimation.setDuration(500);
rotateAnimation.setStartOffset(delay);
rotateAnimation.setFillAfter(true);
layout.startAnimation(rotateAnimation);
}
private static class MyAnimationLisenter implements AnimationListener{
@Override
public void onAnimationStart(Animation animation) {
runningAnimationCount++;
}
@Override
public void onAnimationEnd(Animation animation) {
runningAnimationCount--;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
}
AnimationUtil这是一个工具类,一个被使用的类。相信学过RotateAnimation这个API的小伙伴们对于里面的大多数代码都不会感到陌生。
RotateAnimation是Animation的子类,是一个旋转的动画类,使用它能够达到旋转视图的效果,当然还有透明效果AlphaAnimation、缩放效果ScaleAnimation, 位移效果TranslateAnimation这些动画,这里我就不一一介绍了。
我们看RotateAnimation的构造方法:
RotateAnimation rotateAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 1.0f);
其中的开头的参数(0, -180)代表的是视图的旋转角度从0读的选择为-180度。(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 1.0f)中Animation.RELATIVE_TO_SELF代表这个动画是基于自身旋转的,0.5f表示旋转的中心点为视图宽度的中心位置,1.0f代表的是视图的高度。
这些设定好之后,得到选择的中心点,视图将会从显示的位置旋转到消失的位置。
int childCount = layout.getChildCount();
for (int i = 0; i < childCount; i ++) {
// 设置layout子控件为不可点击
layout.getChildAt(i).setEnabled(false);
}
上面这段代码的意思就是获得所有的对象,然后设置为不可点击。为什么要这么做呢?这就不得不说到补间动画的特点了,补间动画会将视图的位置或者透明度发生变化,但其实际的控件是没有变化的,简单的说就是障眼法。因此如果设置了点击事件,控件仍在当前的位置,就会导致控件可被点击,对于用户来说控件都看不见了,仍然可被点击就是个bug,所以我们需要在视图移出去的时候让视图不可被点击,然后移进来的时候让视图可再次被点击。
最后就是工具类中的内部类MyAnimationLisenter,这是一个负责监听的内部类,它所负责的工作就是给runningAnimationCount赋值。
private static class MyAnimationLisenter implements AnimationListener{
@Override
public void onAnimationStart(Animation animation) {
runningAnimationCount++;
}
@Override
public void onAnimationEnd(Animation animation) {
runningAnimationCount--;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
为什么要给runningAnimationCount赋值呢?原因是我们想点击按钮启动动画之后,突然再次点击按钮,这时我们不想再次启动动画,我们想让动画结束之后才能启动新的动画,这时我们就要监听动画的状态,传出runningAnimationCount的值。如果它大于0,那么动画就正在启动,取消新的点击事件,如果没有大于0,就执行新的动画。
接下来就是调用工具类的AnimationMenuActivity,代码如下:
public class AnimationMenuActivity extends Activity implements OnClickListener{
private RelativeLayout rl_level1;
private RelativeLayout rl_level2;
private RelativeLayout rl_level3;
private boolean isDisplaylevel3 = true;
private boolean isDisplaylevel2 = true;
private boolean isDisplaylevel1 = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_youkumenu);
initView();
}
// 初始化控件
private void initView() {
findViewById(R.id.ib_home).setOnClickListener(this);
findViewById(R.id.ib_menu).setOnClickListener(this);
rl_level1 = (RelativeLayout) findViewById(R.id.rl_level1);
rl_level2 = (RelativeLayout) findViewById(R.id.rl_level2);
rl_level3 = (RelativeLayout) findViewById(R.id.rl_level3);
}
@Override
public void onClick(View v) {
// 如果在动画正在运行的时候点击,那么直接返回,不执行新的动画
if (AnimationUtil.runningAnimationCount > 0) {
return;
}
switch (v.getId()) {
case R.id.ib_home:
// 点击了主页按钮
if (isDisplaylevel2) {
// 设置延时时间
long delay = 0;
if (isDisplaylevel3) {
// 如果菜单已经显示,那么设置不显示
AnimationUtil.RotateAnimationOut(rl_level3, 0);
isDisplaylevel3 = false;
// 当第三级菜单存在是,设置延时时间为200,然后程序往下运行时,二级菜单将会延时执行
delay += 200;
}
AnimationUtil.RotateAnimationOut(rl_level2,delay);
} else {
AnimationUtil.RotateAnimationIn(rl_level2, 0);
}
isDisplaylevel2 = !isDisplaylevel2;
break;
case R.id.ib_menu:
// 点击了菜单按钮
if (isDisplaylevel3) {
// 如果菜单已经显示,那么设置不显示
AnimationUtil.RotateAnimationOut(rl_level3, 0);
} else {
// 如果菜单不显示,那么选择显示出来
AnimationUtil.RotateAnimationIn(rl_level3, 0);
}
isDisplaylevel3 = !isDisplaylevel3;
break;
default:
break;
}
}
// 按下物理按键menu的时候(使用虚拟机时要设置成有物理按键)
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (AnimationUtil.runningAnimationCount > 0) {
return true;
}
if (keyCode == KeyEvent.KEYCODE_MENU) {
long delay = 0;
//判断是否存在1级菜单没有隐藏
if (isDisplaylevel1) {
if (isDisplaylevel3){
// 隐藏三级菜单
AnimationUtil.RotateAnimationOut(rl_level3, delay);
isDisplaylevel3 = false;
delay += 200;
}
if (isDisplaylevel2) {
// 隐藏二级菜单
AnimationUtil.RotateAnimationOut(rl_level2, delay);
isDisplaylevel2 = false;
delay += 200;
}
AnimationUtil.RotateAnimationOut(rl_level1, delay);
} else {
// 如果菜单都被隐藏,那么现实出来
AnimationUtil.RotateAnimationIn(rl_level1, 0);
AnimationUtil.RotateAnimationIn(rl_level2, 200);
AnimationUtil.RotateAnimationIn(rl_level3, 300);
isDisplaylevel2 = true;
isDisplaylevel3 = true;
}
isDisplaylevel1 = !isDisplaylevel1;
return true;
}
// 返回true则代表在onKeyDown中使用了点击事件,这样点击事件就不会被其他代码使用
return super.onKeyDown(keyCode, event);
}
}
这一大串的代码看下来心好累,已经没兴趣做其他的事情了。但是且慢,这些代码其实并没有太过复杂的地方。
这么多的代码其实只说明了两件事情,就是button按键点击事件和(实体)菜单menu点击事件。
在按键点击事件,也就是onClick()方法中,我们做出了判断,如果点击了中间的菜单键R.id.ib_menu,那么就判断是否最外层的菜单存在,如果存在就隐藏,不存在的显示,同时设置isDisplaylevel3的布尔值,作为下次点击事件的判断依据。
如果点击了最里面的主页按键R.id.ib_home,那就判断中间菜单和最外层菜单是否存在,如果两者都在,那么隐藏两者,如果只有中间菜单在那么隐藏中间菜单,如果都不在,就显示中间菜单。
最后就是onKeyDown()实体按键点击事件,这个需要设置虚拟机有实体按键时才会生效。这个按键判断所有的菜单是否存在,哪个菜单存在就因此哪个,如果全部都不在那么就全部显示。
当然,大家还要注意到一点,那就是菜单显示的顺序问题,但所有的菜单都存在时从最外层开始隐藏,然后到中间在到最里面。如果都不在时,就从最里面开始出现。这个菜单的显示顺序也是要注意的一点。