Android进阶之绘制-自定义View完全掌握(一)
android的ui设计可以说是决定一个app质量的关键因素,因为人们在使用app的时候,最先映入眼帘的就是app的界面了,一个美观、充实的界面能够给用户带来非常好的体验,会在用户心中留下好的印象。
对于ui设计,android原生的控件加上一些开源库的使用,已经能够满足大部分的ui需求,但是,某些比较新颖、花哨的控件效果,我们只能通过自定义view来实现,那么,从该篇博客开始,我将记录关于android自定义view的学习内容,并将其整理呈现给大家。
我们来实现一个优酷菜单案例,在案例中会涉及到很多方面的知识。
案例效果如下:
对activity_main.xml文件进行修改。
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.itcast.youkumenu.mainactivity"> <relativelayout android:id="@+id/level1" android:layout_width="100dp" android:layout_height="50dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level1"> </relativelayout> <relativelayout android:id="@+id/level2" android:layout_width="180dp" android:layout_height="90dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level2"> </relativelayout> <relativelayout android:id="@+id/level3" android:layout_width="280dp" android:layout_height="140dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level3"> </relativelayout> </relativelayout>
预览一下效果。
貌似效果已经出来了,但是请注意了,我这样布局的话能点到每个圆环吗?看上面的图片,我只能点到蓝色线条框住的矩形,这是因为小圆环被大圆环覆盖了,我们重新修改一下布局代码。
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.itcast.youkumenu.mainactivity"> <relativelayout android:id="@+id/level3" android:layout_width="280dp" android:layout_height="140dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level3"> </relativelayout> <relativelayout android:id="@+id/level2" android:layout_width="180dp" android:layout_height="90dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level2"> </relativelayout> <relativelayout android:id="@+id/level1" android:layout_width="100dp" android:layout_height="50dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level1"> </relativelayout> </relativelayout>
此时预览一下。
我已经能正确点击到每一个圆环,而我的代码只是将三个相对布局调换了一下位置,既然大圆环会覆盖到小圆环,那我们直接把大圆环放到最上面,这样就不会出现覆盖问题了。
这是我们需要注意的一个点。接下来贴出完整的布局代码。
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.itcast.youkumenu.mainactivity"> <relativelayout android:id="@+id/level3" android:layout_width="280dp" android:layout_height="140dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level3"> <imageview android:id="@+id/channel1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:layout_marginbottom="8dp" android:layout_marginleft="8dp" android:src="@drawable/channel1" /> <imageview android:id="@+id/channel2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/channel1" android:layout_marginbottom="8dp" android:layout_marginleft="33dp" android:src="@drawable/channel2" /> <imageview android:id="@+id/channel3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/channel2" android:layout_marginbottom="8dp" android:layout_marginleft="63dp" android:src="@drawable/channel3" /> <imageview android:id="@+id/channel4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerhorizontal="true" android:layout_margintop="8dp" android:src="@drawable/channel4" /> <imageview android:id="@+id/channel7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:layout_alignparentright="true" android:layout_marginbottom="8dp" android:layout_marginright="8dp" android:src="@drawable/channel7" /> <imageview android:id="@+id/channel6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/channel7" android:layout_alignparentright="true" android:layout_marginbottom="8dp" android:layout_marginright="33dp" android:src="@drawable/channel6" /> <imageview android:id="@+id/channel5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/channel6" android:layout_alignparentright="true" android:layout_marginbottom="8dp" android:layout_marginright="63dp" android:src="@drawable/channel5" /> </relativelayout> <relativelayout android:id="@+id/level2" android:layout_width="180dp" android:layout_height="90dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level2"> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:layout_marginbottom="8dp" android:layout_marginleft="8dp" android:src="@drawable/icon_search" /> <imageview android:id="@+id/icon_menu" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerhorizontal="true" android:layout_margintop="8dp" android:src="@drawable/icon_menu" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:layout_alignparentright="true" android:layout_marginbottom="8dp" android:layout_marginright="8dp" android:src="@drawable/icon_myyouku" /> </relativelayout> <relativelayout android:id="@+id/level1" android:layout_width="100dp" android:layout_height="50dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:background="@drawable/level1"> <imageview android:id="@+id/icon_home" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerinparent="true" android:src="@drawable/icon_home" /> </relativelayout> </relativelayout>
这样布局我们就完成了,接下来我们要实现功能了。
我们可以把功能分为两层,我们先完成中间圆环菜单键控制最外层圆环旋转的动画。然后再完成最里层圆环home键控制中间圆环旋转的动画。
修改mainactivity的代码。
package com.itcast.youkumenu; import android.os.bundle; import android.support.v7.app.appcompatactivity; import android.view.view; import android.widget.imageview; import android.widget.relativelayout; import android.widget.toast; public class mainactivity extends appcompatactivity { private relativelayout level1; private relativelayout level2; private relativelayout level3; private imageview icon_menu; private imageview icon_home; /** * 是否显示最外层圆环 * true:显示 * false:隐藏 */ private boolean isshowlevel3 = true; /** * 是否显示中间圆环 * true:显示 * false:隐藏 */ private boolean isshowlevel2 = true; /** * 是否显示最里层圆环 * true:显示 * false:隐藏 */ private boolean isshowlevel1 = true; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); } private void initview() { level3 = (relativelayout) findviewbyid(r.id.level3); icon_menu = (imageview) findviewbyid(r.id.icon_menu); level2 = (relativelayout) findviewbyid(r.id.level2); icon_home = (imageview) findviewbyid(r.id.icon_home); level1 = (relativelayout) findviewbyid(r.id.level1); myonclicklistener myonclicklistener = new myonclicklistener(); //设置点击事件 icon_home.setonclicklistener(myonclicklistener); icon_menu.setonclicklistener(myonclicklistener); } class myonclicklistener implements view.onclicklistener { @override public void onclick(view v) { switch (v.getid()) { case r.id.icon_home://home键 //如果最外层菜单和中间菜单都是显示的,就都设置为隐藏 if(isshowlevel2){ //隐藏中间菜单 isshowlevel2 = false; tools.hideview(level2); if(isshowlevel3){ //隐藏最外层菜单 isshowlevel3 = false; tools.hideview(level3,200); } }else{ //显示中间菜单 isshowlevel2 = true; tools.showview(level2); } //如果都是隐藏的,就仅显示中间菜单 break; case r.id.icon_menu://菜单键 if(isshowlevel3){ //隐藏 isshowlevel3 = false; tools.hideview(level3); }else{ //显示 isshowlevel3 = true; tools.showview(level3); } break; } } } }
在显示和隐藏布局的时候,我抽出了一个工具类用于实现旋转动画,工具类代码如下。
package com.itcast.youkumenu; import android.view.view; import android.view.animation.rotateanimation; import android.widget.relativelayout; /** * 显示和隐藏指定的控件 */ class tools { public static void hideview(view view) { hideview(view,0); } public static void showview(view view) { rotateanimation ra = new rotateanimation(180, 360, view.getwidth() / 2, view.getheight()); ra.setduration(500);//设置动画播放持续时间 ra.setfillafter(true);//动画停留在播放完成的状态 view.startanimation(ra); } public static void hideview(view view, int startoffset) { rotateanimation ra = new rotateanimation(0, 180, view.getwidth() / 2, view.getheight()); ra.setduration(500);//设置动画播放持续时间 ra.setfillafter(true);//动画停留在播放完成的状态 ra.setstartoffset(startoffset);//设置动画延迟时间 view.startanimation(ra); } }
运行项目,效果如下。
现在我们要实现一下点击menu键也能隐藏圆环。要想实现这个效果,就得对手机按钮进行控制。
重新修改mainactivity的代码。
package com.itcast.youkumenu; import android.os.bundle; import android.support.v7.app.appcompatactivity; import android.view.keyevent; import android.view.view; import android.widget.imageview; import android.widget.relativelayout; import android.widget.toast; public class mainactivity extends appcompatactivity { private relativelayout level1; private relativelayout level2; private relativelayout level3; private imageview icon_menu; private imageview icon_home; /** * 是否显示最外层圆环 * true:显示 * false:隐藏 */ private boolean isshowlevel3 = true; /** * 是否显示中间圆环 * true:显示 * false:隐藏 */ private boolean isshowlevel2 = true; /** * 是否显示最里层圆环 * true:显示 * false:隐藏 */ private boolean isshowlevel1 = true; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); } private void initview() { level3 = (relativelayout) findviewbyid(r.id.level3); icon_menu = (imageview) findviewbyid(r.id.icon_menu); level2 = (relativelayout) findviewbyid(r.id.level2); icon_home = (imageview) findviewbyid(r.id.icon_home); level1 = (relativelayout) findviewbyid(r.id.level1); myonclicklistener myonclicklistener = new myonclicklistener(); //设置点击事件 icon_home.setonclicklistener(myonclicklistener); icon_menu.setonclicklistener(myonclicklistener); } class myonclicklistener implements view.onclicklistener { @override public void onclick(view v) { switch (v.getid()) { case r.id.icon_home://home键 //如果最外层菜单和中间菜单都是显示的,就都设置为隐藏 if(isshowlevel2){ //隐藏中间菜单 isshowlevel2 = false; tools.hideview(level2); if(isshowlevel3){ //隐藏最外层菜单 isshowlevel3 = false; tools.hideview(level3,200); } }else{ //显示中间菜单 isshowlevel2 = true; tools.showview(level2); } //如果都是隐藏的,就仅显示中间菜单 break; case r.id.icon_menu://菜单键 if(isshowlevel3){ //隐藏 isshowlevel3 = false; tools.hideview(level3); }else{ //显示 isshowlevel3 = true; tools.showview(level3); } break; } } } @override public boolean onkeydown(int keycode, keyevent event) { if (keycode == keyevent.keycode_menu){ //如果三个圆环是显示的,就全部隐藏 if (isshowlevel1){ isshowlevel1 = false; tools.hideview(level1); if(isshowlevel2){ //隐藏中间圆环 isshowlevel2 = false; tools.hideview(level2,200); if (isshowlevel3){ //隐藏最外层圆环 isshowlevel3 = false; tools.hideview(level3,400); } } }else{ //如果最里层圆环和中间圆环是隐藏的,就显示 //显示最里层圆环 isshowlevel1 = true; tools.showview(level1); //显示中间圆环 isshowlevel2 = true; tools.showview(level2,200); } return true; } return super.onkeydown(keycode, event); } }
工具类代码。
package com.itcast.youkumenu; import android.view.view; import android.view.animation.rotateanimation; import android.widget.relativelayout; /** * 显示和隐藏指定的控件 */ class tools { public static void hideview(view view) { hideview(view,0); } public static void showview(view view) { showview(view,0); } public static void hideview(view view, int startoffset) { rotateanimation ra = new rotateanimation(0, 180, view.getwidth() / 2, view.getheight()); ra.setduration(500);//设置动画播放持续时间 ra.setfillafter(true);//动画停留在播放完成的状态 ra.setstartoffset(startoffset);//设置动画延迟时间 view.startanimation(ra); } public static void showview(view view, int startoffset) { rotateanimation ra = new rotateanimation(180, 360, view.getwidth() / 2, view.getheight()); ra.setduration(500);//设置动画播放持续时间 ra.setfillafter(true);//动画停留在播放完成的状态 ra.setstartoffset(startoffset); view.startanimation(ra); } }
运行项目,效果如下。
这样就实现了再点击手机的menu键时圆环旋转消失,但是这样就产生了一个bug,不知道观察了上面的动图大家发现bug没有,当我按menu键隐藏圆环时,我再去点击圆环的位置,圆环还是旋转出来了,按道理我们的圆环消失后,就不能被我们点击出来了吧。这里就涉及到了普通动画和属性动画的区别了。当然,解决办法有很多,我这里介绍两种。
第一种,给每个孩子设置不可以点击。
那很多人就有点子了,可以在tools类的hideview()方法中添加view.setenabled(false);
,然后在showview()方法中添加view.setenabled(true);
,有些人以为这样就能够解决bug了。其实以为这样可以解决问题的人,他就不了解view和viewgroup的区别,view是不能够对孩子进行操作的,而我们在方法中将传递过来的布局转换为了view,它原先的某些属性就丢失了。其实,对view参数设置了不可点击的话,相对布局确实变得无法被点击了,但是,它的孩子还是可以被点击的。那应该怎么办呢?我们把tools类中的四个方法的view参数全部改为viewgroup,然后对viewgroup的孩子进行禁止点击的操作。具体代码如下。
package com.itcast.youkumenu; import android.view.view; import android.view.viewgroup; import android.view.animation.rotateanimation; import android.widget.relativelayout; /** * 显示和隐藏指定的控件 */ class tools { public static void hideview(viewgroup view) { hideview(view,0); } public static void showview(viewgroup view) { showview(view,0); } public static void hideview(viewgroup view, int startoffset) { rotateanimation ra = new rotateanimation(0, 180, view.getwidth() / 2, view.getheight()); ra.setduration(500);//设置动画播放持续时间 ra.setfillafter(true);//动画停留在播放完成的状态 ra.setstartoffset(startoffset);//设置动画延迟时间 view.startanimation(ra); for(int i = 0;i < view.getchildcount();i++){ view childview = view.getchildat(i); //设置不可以点击 childview.setenabled(false); } } public static void showview(viewgroup view, int startoffset) { rotateanimation ra = new rotateanimation(180, 360, view.getwidth() / 2, view.getheight()); ra.setduration(500);//设置动画播放持续时间 ra.setfillafter(true);//动画停留在播放完成的状态 ra.setstartoffset(startoffset); view.startanimation(ra); for(int i = 0;i < view.getchildcount();i++){ view childview = view.getchildat(i); //设置不可以点击 childview.setenabled(true); } } }
这时你再运行项目,然后点击menu键隐藏圆环后,不管你怎么点击,圆环都不会再出来了。
第二种方法,前面也说到了,我们可以通过属性动画解决该bug。
属性动画和普通动画的区别在于,普通动画只有视觉效果,而控件不会改变它的位置;属性动画不仅有动画效果,而且控件会随着动画而改变位置。可以想象,使用属性动画来旋转的话,当动画执行完毕时,布局旋转180度,此时控件都会旋转到屏幕的下方,这样,用户就点击不到控件从而也就不能触发点击事件了。
修改工具类代码。
package com.itcast.youkumenu; import android.animation.objectanimator; import android.view.view; import android.view.viewgroup; import android.view.animation.rotateanimation; import android.widget.relativelayout; /** * 显示和隐藏指定的控件 */ class tools { public static void hideview(viewgroup view) { hideview(view, 0); } public static void showview(viewgroup view) { showview(view, 0); } public static void hideview(viewgroup view, int startoffset) { // rotateanimation ra = new rotateanimation(0, 180, view.getwidth() / 2, view.getheight()); // ra.setduration(500);//设置动画播放持续时间 // ra.setfillafter(true);//动画停留在播放完成的状态 // ra.setstartoffset(startoffset);//设置动画延迟时间 // view.startanimation(ra); // // for(int i = 0;i < view.getchildcount();i++){ // view childview = view.getchildat(i); // //设置不可以点击 // childview.setenabled(false); // } //改为属性动画 objectanimator animator = objectanimator.offloat(view, "rotation", 0, 180); animator.setduration(500); animator.setstartdelay(startoffset); animator.start(); view.setpivotx(view.getwidth() / 2); view.setpivoty(view.getheight()); } public static void showview(viewgroup view, int startoffset) { // rotateanimation ra = new rotateanimation(180, 360, view.getwidth() / 2, view.getheight()); // ra.setduration(500);//设置动画播放持续时间 // ra.setfillafter(true);//动画停留在播放完成的状态 // ra.setstartoffset(startoffset); // view.startanimation(ra); // // for (int i = 0; i < view.getchildcount(); i++) { // view childview = view.getchildat(i); // //设置不可以点击 // childview.setenabled(true); // } //改为属性动画 objectanimator animator = objectanimator.offloat(view, "rotation", 180, 360); animator.setduration(500); animator.setstartdelay(startoffset); animator.start(); view.setpivotx(view.getwidth() / 2); view.setpivoty(view.getheight()); } }
运行项目,效果和原来相同,但是bug却已经解决了,大家可以自己试一试,原理我也已经说过了。对于动画,android中分了三个部分,补间动画、帧动画、属性动画,动画的话,我也会在今后的博客中专门讲解一下。那么今天的内容就到这里了。
上一篇: Mysql主从复制原理及搭建
下一篇: 古代官员有假期吗?揭秘各个朝代的休假制度
推荐阅读
-
Android进阶之绘制-自定义View完全掌握(二)
-
Android进阶之绘制-自定义View完全掌握(一)
-
Android进阶之绘制-自定义View完全掌握(三)
-
Android进阶之光笔记:自定义View《一》
-
Android进阶之绘制-自定义View完全掌握(一)
-
写一个轻量的用户引导工具:Android自定义View之GuideView
-
Android进阶之绘制-自定义View完全掌握(二)
-
Android进阶之绘制-自定义View完全掌握(三)
-
Android进阶之光笔记:自定义View《一》
-
一起Talk Android吧(第一百六十回:Android自定义View之ViewGroup实例一)