这篇文章主要为大家详细介绍了简单实用的 Android UI 微博动态点赞效果, 具有一定的参考价值,感兴趣的小伙伴们可以参考一下
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
说起空间动态、微博的点赞效果,网上也是很泛滥,各种实现与效果一大堆。而详细实现的部分,讲述的也是参差不齐,另一方面估计也有很多大侠也不屑一顾,觉得完全没必要单独开篇来写和讲解吧。毕竟,也就是两个 view 和一些简单的动画效果罢了。
单若是只讲这些,我自然也是不愿花这番功夫的。虽然自己很菜,可也不甘于太菜。所以偶尔看到些好东西,可以延伸学写下,我还是很情愿拿出来用用,顺带秀一秀逼格什么的。
不扯太多,先说说今天实现点赞效果用到的自以为不错的两个点:
Checkable 用来扩展 View 实现选中状态切换
AndroidViewAnimations 基于 nineoldandroids 封装的 android 动画简易类库。究竟有多简单呢,就像这样
当然是从实现角度来看这个库啦,如果仅仅是使用,google / 百度一大堆啦。
结合前两篇富 {aa0aa},加上我们的点赞 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" 的效果,图片切换到选中状态;再点则恢复未选中,而且不会触发动画。
至此,点赞这块内容和关注点也说完了,希望各位能有点儿收获,另外便于自己也能加深理解。
最后,附上示例源码地址:
来源: http://www.phperz.com/article/17/0314/302414.html