简单实用的Android UI微博动态点赞效果
说起空间动态、微博的点赞效果,网上也是很泛滥,各种实现与效果一大堆。而详细实现的部分,讲述的也是参差不齐,另一方面估计也有很多大侠也不屑一顾,觉得完全没必要单独开篇来写和讲解吧。毕竟,也就是两个view和一些简单的动画效果罢了。
单若是只讲这些,我自然也是不愿花这番功夫的。虽然自己很菜,可也不甘于太菜。所以偶尔看到些好东西,可以延伸学写下,我还是很情愿拿出来用用,顺带秀一秀逼格什么的。
不扯太多,先说说今天实现点赞效果用到的自以为不错的两个点:
checkable 用来扩展view实现选中状态切换
androidviewanimations 基于nineoldandroids封装的android动画简易类库。究竟有多简单呢,就像这样
animhelper.with(new pulseanimator()).duration(1000).playon(imageview);
作用: 在imageview上使用pulseanimator这个动画效果,播放一秒。
当然是从实现角度来看这个库啦,如果仅仅是使用,google/百度一大堆啦。
结合前两篇富,加上我们的点赞view 做出的demo整合效果图:
1.从实现看门道
其实从效果看无非就是点击切换图片,并添加一些简单动画效果而已,确实没什么难度。这里是因为引入了两个不错的新内容,使用下,权当新手尝鲜。
1.1 checkable接口实现checkedimageview
系统本身提供了android.widget.checkable这样一个接口,方便我们继承实现view的选中和取消的状态。看下这个类:
public interface checkable { /** * 设置view的选中状态 */ void setchecked(boolean checked); /** * 当前view是否被选中 */ boolean ischecked(); /** *改变view的选中状态到相反的状态 */ void toggle(); }
通常这个接口用来帮助我们快速实现view的可选效果,增加了选中和取消两种状态和切换方法。另外为了方便view在状态改变时候快速地变看到效果(更背景或图片),我们可以直接通过selector控制图片,而其本身并不会自动改变drawable状态,因此这里还有必要重写drawablestatechanged
方法。我们先以定义一个通用的checkedimageview为例:
public class checkedimageview extends imageview implements checkable{ protected boolean ischecked;//选中状态 protected oncheckedchangelistener oncheckedchangelistener;//状态改变事件监听 public static final int[] checked_state_set = { android.r.attr.state_checked }; public checkedimageview(context context) { super(context); initialize(); } public checkedimageview(context context, attributeset attrs) { super(context, attrs); initialize(); } private void initialize() { ischecked = false; } @override public boolean ischecked() { return ischecked; } @override public void setchecked(boolean ischecked) { if (this.ischecked != ischecked) { this.ischecked = ischecked; refreshdrawablestate(); if (oncheckedchangelistener != null) { oncheckedchangelistener.oncheckedchanged(this, ischecked); } } } @override public void toggle() {//改变状态 setchecked(!ischecked); } //初始drawablestate时候为它添加一个checked_state,imageview本身是没有这个状态的 @override public int[] oncreatedrawablestate(int extraspace) { int[] states = super.oncreatedrawablestate(extraspace + 1); if (ischecked()) { mergedrawablestates(states, checked_state_set); } return states; } //当view的选中状态被改变的时候通知imageview改变背景或内容,这个view会自动在drawable状态集中选择与当前状态匹配的图片 @override protected void drawablestatechanged() { super.drawablestatechanged(); drawable drawable = getdrawable(); if (drawable != null) { int[] mydrawablestate = getdrawablestate(); drawable.setstate(mydrawablestate); invalidate(); } } //设置状态改变监听事件 public void setoncheckedchangelistener(oncheckedchangelistener oncheckedchangelistener) { this.oncheckedchangelistener = oncheckedchangelistener; } //当选中状态改变时监听接口触发该事件 public static interface oncheckedchangelistener { public void oncheckedchanged(checkedimageview checkedimgeview, boolean ischecked); } }
这是一个通用的可被选中imageview,当点击之后被选中,再次点击则取消。而其背景/内容也会随之改变。比如下图所示效果:
从代码上看,我们本身并没有直接定义当view点击之后,调用setimage()或者setbackground()来改变内容,而是通过使用view本身的drawablestate来绘制和更改,查找与它对应匹配的图片,而这些状态所对应的图片,都预先在selector中配置好。关于selector这里不做介绍,自行查阅学习。
既然提到selector,顺带提下之前遇到的坑,关于他的匹配原则。比如下边这样一个selector:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/icon_pressed"></item> <item android:state_checked="true" android:drawable="@drawable/icon_checked"></item> <item android:drawable="@drawable/icon_normal"></item> </selector>
当view同时有上边两个状态(如state_pressed和state_checked)的时候,view优先显示第一个状态时候的图片(icon_pressed)。这是因为它是由上到下有序查找的,当找到第一个状态与他定义的所相符所在行时,就优先显示这行的图片。所以当我们将最后一行
< item android:drawable=”@drawable/icon_normal”>< /item>
放在第一行时,无论是否选中状态或按下状态,都显示的是icon_normal。初学者一定要注意,我当初就因为这个原因耗费了很多时间查找缘由。
回到我们的点赞实现。这里实现的点赞view praiseview 包含了一个 checkedimageview 和一个 textview ,点赞之后,imageview会放大回缩并弹出一个textview”+1”的动画效果。
public class praiseview extends framelayout implements checkable{//同样继承checkable protected onpraischeckedlistener praisecheckedlistener; protected checkedimageview imageview; //点赞图片 protected textview textview; //+1 protected int padding; public praiseview(context context) { super(context); initalize(); } public praiseview(context context, attributeset attrs) { super(context, attrs); initalize(); } protected void initalize() { setclickable(true); imageview = new checkedimageview(getcontext()); imageview.setimageresource(r.drawable.blog_praise_selector); framelayout.layoutparams flp = new layoutparams(framelayout.layoutparams.wrap_content, framelayout.layoutparams.wrap_content,gravity.center); addview(imageview, flp); textview = new textview(getcontext()); textview.settextsize(10); textview.settext("+1"); textview.settextcolor(color.parsecolor("#a24040")); textview.setgravity(gravity.center); addview(textview, flp); textview.setvisibility(view.gone); } @override public boolean performclick() { checkchange(); return super.performclick(); } @override public void toggle() { checkchange(); } public void setchecked(boolean ischeacked) { imageview.setchecked(ischeacked); } public void checkchange() {//点击切换状态 if (imageview.ischecked) { imageview.setchecked(false); } else { imageview.setchecked(true); textview.setvisibility(view.visible); //放大动画 animhelper.with(new pulseanimator()).duration(1000).playon(imageview); //飘 “+1”动画 animhelper.with(new slideoutupanimator()).duration(1000).playon(textview); } //调用点赞事件 if (praisecheckedlistener != null) { praisecheckedlistener.onpraischecked(imageview.ischecked); } } public boolean ischecked() { return imageview.ischecked; } public void setonpraischeckedlistener(onpraischeckedlistener praisecheckedlistener) { this.praisecheckedlistener = praisecheckedlistener; } public interface onpraischeckedlistener{ void onpraischecked(boolean ischecked); } }
过于自定的view大概就这么多了,checkable这个小巧方便的类,不知道你会用了没。至于上边用到的两个动画效果集:
animhelper.with(new pulseanimator()).duration(1000).playon(imageview);
animhelper.with(new slideoutupanimator()).duration(1000).playon(textview);
感觉封装的挺简洁实用,所以很有必要学习分析一下。
1.2 动画库的封装和快速框架
提到动画,android本身自带的动画类animation已经做到支持3.0及以上了,虽然也做了很好的封装,但是做起复杂动画来还是不够像上边那样简洁。在关于动画兼容方面,github上的大牛jake wharton又做了一套动画开源库nineoldandroids,效果很好而且支持3.0级以前的版本,确实很值得称赞。而在此基础上,有很多高手又做了二次封装,实现了复杂动画,同时保证方便简洁,而且通用性和扩展性更高。我们这里的动画使用的就是这样一个简单的封装。
比如,要在xxview上时用xxanimator这样的动画,持续duration秒。就这么一行代码:
animhelper.with(new slideoutupanimator()).duration(1000).playon(textview);
来看一下基于nineoldandroids的viewanimations具体实现。
1). 首先定义一个基本动画效果类baseviewanimator
这个baseviewanimator动画类使用一个动画集合animatorset,包装成单个动画类似的用法,并定义了一个abstract方法prepare():
public abstract class baseviewanimator { public static final long duration = 1000; private animatorset manimatorset; private long mduration = duration; { manimatorset = new animatorset(); } protected abstract void prepare(view target); public baseviewanimator settarget(view target) { reset(target); prepare(target); return this; } public void animate() { start(); } /** * reset the view to default status * * @param target */ public void reset(view target) { viewhelper.setalpha(target, 1); viewhelper.setscalex(target, 1); viewhelper.setscaley(target, 1); viewhelper.settranslationx(target, 0); viewhelper.settranslationy(target, 0); viewhelper.setrotation(target, 0); viewhelper.setrotationy(target, 0); viewhelper.setrotationx(target, 0); viewhelper.setpivotx(target, target.getmeasuredwidth() / 2.0f); viewhelper.setpivoty(target, target.getmeasuredheight() / 2.0f); } /** * start to animate */ public void start() { manimatorset.setduration(mduration); manimatorset.start(); } public baseviewanimator setduration(long duration) { mduration = duration; return this; } public baseviewanimator setstartdelay(long delay) { getanimatoragent().setstartdelay(delay); return this; } public long getstartdelay() { return manimatorset.getstartdelay(); } public baseviewanimator addanimatorlistener(animatorlistener l) { manimatorset.addlistener(l); return this; } public void cancel(){ manimatorset.cancel(); } public boolean isrunning(){ return manimatorset.isrunning(); } public boolean isstarted(){ return manimatorset.isstarted(); } public void removeanimatorlistener(animatorlistener l) { manimatorset.removelistener(l); } public void removealllistener() { manimatorset.removealllisteners(); } public baseviewanimator setinterpolator(interpolator interpolator) { manimatorset.setinterpolator(interpolator); return this; } public long getduration() { return mduration; } public animatorset getanimatoragent() { return manimatorset; } }
复杂动画效果基类baseviewanimator使用一个animatorset集合来添加各种动画 ,并绑定到目标targetview ,使用 prepare(view target) 的abstract方法供其子类实现具体的动画效果。
2). 其次基于这个类实现我们的各种动画效果xxanimator
当我们要实现具体的动画效果时,可以直接继承这个类并实现prepaer方法。比如这里定义的上划消失slideoutupanimator 和放大回缩动画pulseanimator
/** *上划消失(飘+1) */ public class slideoutupanimator extends baseviewanimator { @override public void prepare(view target) { viewgroup parent = (viewgroup)target.getparent(); getanimatoragent().playtogether( objectanimator.offloat(target, "alpha", 1, 0), objectanimator.offloat(target,"translationy",0,-parent.getheight()/2) ); } } /** *放大效果 */ public class pulseanimator extends baseviewanimator { @override public void prepare(view target) { getanimatoragent().playtogether( objectanimator.offloat(target, "scaley", 1, 1.2f, 1), objectanimator.offloat(target, "scalex", 1, 1.2f, 1) ); } }
上边两种动画效果就是对baseviewanimator的两种实现,动画本身使用的库是nineoldandroids。
3). 最后封装一个动画管理工具类animhelper供外部使用
首先定义了一个静态类,使用helper来实例化这个静态类,并设置各个参数选项。
public class animhelper { private static final long duration = baseviewanimator.duration; private static final long no_delay = 0; /** *实例化得到animationcomposer的唯一接口 */ public static animationcomposer with(baseviewanimator animator) { return new animationcomposer(animator); } /** *定义与动画效果相关联的各种参数, *使用这种方法可以保证对象的构建和他的表示相互隔离开来 */ public static final class animationcomposer { private list<animator.animatorlistener> callbacks = new arraylist<animator.animatorlistener>(); private baseviewanimator animator; private long duration = duration; private long delay = no_delay; private interpolator interpolator; private view target; private animationcomposer(baseviewanimator animator) { this.animator = animator; } public animationcomposer duration(long duration) { this.duration = duration; return this; } public animationcomposer delay(long delay) { this.delay = delay; return this; } public animationcomposer interpolate(interpolator interpolator) { this.interpolator = interpolator; return this; } public animationcomposer withlistener(animator.animatorlistener listener) { callbacks.add(listener); return this; } public animmanager playon(view target) { this.target = target; return new animmanager(play(), this.target); } private baseviewanimator play() { animator.settarget(target); animator.setduration(duration) .setinterpolator(interpolator) .setstartdelay(delay); if (callbacks.size() > 0) { for (animator.animatorlistener callback : callbacks) { animator.addanimatorlistener(callback); } } animator.animate(); return animator; } } /** *动画管理类 */ public static final class animmanager{ private baseviewanimator animator; private view target; private animmanager(baseviewanimator animator, view target){ this.target = target; this.animator = animator; } public boolean isstarted(){ return animator.isstarted(); } public boolean isrunning(){ return animator.isrunning(); } public void stop(boolean reset){ animator.cancel(); if(reset) animator.reset(target); } } }
这段代码使用了类似dialog的builder模式,感兴趣的可以搜一下 java设计模式-builder.晚点会另开一篇讲解。
(注: 复杂动画这一部分的内容这里只是拿出来展示和使用,包装和实现是由代码家大大原创,有想了解更多动画及效果的请点其名字链接)
运行一下,就可以看到前面所演示的效果了。点击第一下,,伴随着图标变大一下并飘出“+1”的效果,图片切换到选中状态;再点则恢复未选中,而且不会触发动画。
至此,点赞这块内容和关注点也说完了,希望各位能有点儿收获,另外便于自己也能加深理解。
最后,附上示例源码地址:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: Java 字符串连接的性能问题分析