UI--微博(动态)点赞,简单效果中的不简单门道

《代码里的世界》UI篇

用文字札记描绘自己 android学习之路

转载请保留出处 by Qiao

http://blog.csdn.net/qiaoidea/article/details/45850641

【导航】

- 单行文本水平触摸滑动效果 通过EditText实现TextView单行长文本水平滑动效果

- 多行文本折叠展开 自定义布局View实现多行文本折叠和展开


1.概述

  说起空间动态、微博的点赞效果,网上也是很泛滥,各种实现与效果一大堆。而详细实现的部分,讲述的也是参差不齐,另一方面估计也有很多大侠也不屑一顾,觉得完全没必要单独开篇来写和讲解吧。毕竟,也就是两个view和一些简单的动画效果罢了。

  单若是只讲这些,我自然也是不愿花这番功夫的。虽然自己很菜,可也不甘于太菜。所以偶尔看到些好东西,可以延伸学写下,我还是很情愿拿出来用用,顺带秀一秀逼格什么的。

  不扯太多,先说说今天实现点赞效果用到的自以为不错的两个点:

  • Checkable 用来扩展View实现选中状态切换
  • AndroidViewAnimations 基于nineoldandroids封装的android动画简易类库。究竟有多简单呢,就像这样

    AnimHelper.with(new PulseAnimator()).duration(1000).playOn(imageView);

    作用: 在imageView上使用PulseAnimator这个动画效果,播放一秒。

      当然是从实现角度来看这个库啦,如果仅仅是使用,google/百度一大堆啦。

      

    结合前两篇富文本折叠展开,加上我们的点赞view 做出的demo整合效果图:


2.从实现看门道

  其实从效果看无非就是点击切换图片,并添加一些简单动画效果而已,确实没什么难度。这里是因为引入了两个不错的新内容,使用下,权当新手尝鲜。

2.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);

感觉封装的挺简洁实用,所以很有必要学习分析一下。

2.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”的效果,图片切换到选中状态;再点则恢复未选中,而且不会触发动画。

 

 至此,点赞这块内容和关注点也说完了,希望各位能有点儿收获,另外便于自己也能加深理解。

 最后,附上示例源码地址:

 

 点击下载源码示例demo


时间: 2024-10-13 18:16:31

UI--微博(动态)点赞,简单效果中的不简单门道的相关文章

QQ空间自动评论自动点赞 微博自动点赞自动转发神器带源码(超简单)

原理:先找到对应的图,得到坐标点击.或转发,向下翻页.自动巡屏.关于怎么判断是否已经赞或已经转发,是通过图片不一样,或者把屏从上到下分多个块实现的. // QQ空间自动点赞机 小黄人为您点赞. //12行代码实现.源码:Rem head FindPic 0,0,1024,768,"Attachment:\zan.bmp",1,intX,intY If intX > 0 And intY > 0 Then MoveTo intX+5, intY+5 Delay 100 Lef

提示框第三方库之MBProgressHUD iOS toast效果 动态提示框效果

提示框第三方库之MBProgressHUD iOS toast效果 动态提示框效果 2014-08-11 17:39 11614人阅读 评论(0) 收藏 举报  分类: iOS相关(20)  文章来自:http://blog.csdn.net/ryantang03/article/details/7877120 MBProgressHUD是一个开源项目,实现了很多种样式的提示框,使用上简单.方便,并且可以对显示的内容进行自定义,功能很强大,很多项目中都有使用到.到GitHub上可以下载到项目源码

iOS开发UI篇—实现一个简单的手势解锁应用(基本)

iOS开发UI篇—实现一个简单的手势解锁应用(基本) 一.实现效果 实现效果图: 二.手势解锁应用分析 1.监听手指在view上的移动,首先肯定需要自定义一个view,重写touch began,touch move等方法,当手指移动到圈上时,让其变亮.可以通过button按钮来实现. 2.界面搭建 背景图片(给控制器的view添加一个imageview,设置属性背景图片) 九个按钮(把九个按钮作为一个整体,使用一个大的view来管理这些小的view,这些小的view就是9个button.如果使

iOS开发UI篇—实现一个简单的手势解锁应用(完善)

iOS开发UI篇—实现一个简单的手势解锁应用(完善) 一.需要实现的效果 二.应用完善 1.绘制不处于按钮范围内的连线 2.解决bug(完善) bug1:如果在began方法中通知view绘图,那么会产生bug.因为,当前点没有清空,在手指移开之后要清空当前点.可以在绘制前进行判断,如果当前点是(0,0)那么就不划线.或者在began方法中不进行重绘. bug2:无限菊花.自定义view的背景色为默认的(黑色),只要重写了drawrect方法,view默认的背景颜色就是黑色的,因为上下文默认的颜

Android UI之自定义——最简单的仿QQ音乐歌词颜色渐变

Android UI之自定义--最简单的仿QQ音乐歌词颜色渐变 记得刚开始做android的时候,就发现QQ音乐歌词颜色渐变的效果,就在网上搜索过,但是就是没有找到满意的.今天突然用QQ音乐听歌的时候,看到歌词颜色渐变,决定来分析看看,没想到实现原来如此简单.这篇只是将最简单的歌词颜色渐变功能,不包括歌词滚动等效果. 首先来看下QQ音乐歌词界面 实现步骤 从界面上可以看出,是通过不同颜色的文本叠加所形成的视觉效果.那么android文本一般使用TextView实现,那就来试试用TextView在

iOS开发UI篇—xib的简单使用

iOS开发UI篇—xib的简单使用 一.简单介绍 xib和storyboard的比较,一个轻量级一个重量级. 共同点: 都用来描述软件界面 都用Interface Builder工具来编辑 不同点: Xib是轻量级的,用来描述局部的UI界面 Storyboard是重量级的,用来描述整个软件的多个界面,并且能展示多个界面之间的跳转关系 二.xib的简单使用 1.建立xib文件 建立的xib文件命名为appxib.xib 2.对xib进行设置 根据程序的需要,这里把view调整为自由布局 建立vie

文顶顶 iOS开发UI篇—xib的简单使用

iOS开发UI篇—xib的简单使用 一.简单介绍 xib和storyboard的比较,一个轻量级一个重量级. 共同点: 都用来描述软件界面 都用Interface Builder工具来编辑 不同点: Xib是轻量级的,用来描述局部的UI界面 Storyboard是重量级的,用来描述整个软件的多个界面,并且能展示多个界面之间的跳转关系 二.xib的简单使用 1.建立xib文件 建立的xib文件命名为appxib.xib 2.对xib进行设置 根据程序的需要,这里把view调整为自由布局 建立vie

JS模仿腾讯微博app撕纸效果

本来想用css3来实现,但后来脑袋一热就用了js,省的别人你ie怎么没效果啊!在腾讯微博app上看到的一个效果,鼠标击哪里就撕了哪里,跟撕报纸似的,任意点击左边面的灰色区域,查看效果,当时觉得很有意思,问了下高人,突然觉悟了,原来如此. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dt

WPF中动态更新TextBlock文字中的超链接,文本

1.------------------------------------------------------------------------- 修改超链接的文本文字: <TextBlock><Hyperlink> <TextBlock  x:Name="TextBlockNeedChange" Text="改变的文本" /> </Hyperlink></TextBlock> 修改TextBlockN