一个模仿探探头像编辑效果解析

此前一直在做模仿一个探探头像编辑的效果,但是水平不够一直没做出来,最后看到了丶亲一口就跑的源码

(http://www.apkbus.com/forum.php?mod=viewthread&tid=255164&highlight=%E6%8E%A2%E6%8E%A2 ),恍然醒悟,对我的一些知识有全面提升。我觉得最主要的这个 AnimatorSet 不了解,当时一直在思考如何将多个动画同时执行在我的知识里就一个AnimationSet这个动画集合,但是做不到同时播放。因此没有做出这个效果。

好了不多说了开始随源码一起讲了。

先来看一下布局文件吧。

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    tools:context="com.szh.tantanphoto.ui.MainActivity" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <com.szh.tantanphoto.dragalbum.AlbumView
            android:id="@+id/imageListView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <GridLayout
        android:id="@+id/Rootlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </GridLayout>

</RelativeLayout>

通过布局文件可以看出GridLayout和自定义的ViewGroup是有一部分是重合的。这个到代码后面GridLayout的作用就是让图片全屏拖动。

在他这个Demo整个的流程是开始在MainActivity里面执行oncreate开始实例化我们自定义的ViewGroup,在实例化ViewGroup的时候配置图片信息和初始化padding值

    public AlbumView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mImageOptions = ImageUtils.getFaceVideoOptions();
        padding = dp2px(4, context);
    }

然后在MainActivity的oncreate里面获取GridLayout并传如ViewGroup里面,并且传入一个图片对象集合进去,给ViewGroup设置其点击子控件事件。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mAlbumView = getId(R.id.imageListView);
        mAlbumView.setRootView((GridLayout) getId(R.id.Rootlayout));
        mAlbumView.setImages(new DemoUtils().moarItems(6, getImageDate()));
        mAlbumView.setOnItemClickListener(this);
    }

将图片集合传进来后就开始往ViewGroup里面填充子控件,并设置Tag和触摸事件

    /**
     * 初始化View集合,并向views里面添加数据
     */
    public void initUI() {
        /**
         * 清空集合
         */
        views.clear();
        /**
         * 清楚所有的view对象
         */
        removeAllViews();
        for (int i = 0; i < images.size(); i++) {
            ImageView view = new ImageView(getContext());
            /**
             * 给ImageView设置填充父类布局的属性
             */
            view.setScaleType(ScaleType.FIT_XY);
            if (!StringUtils.isEmpty(images.get(i).hyperlink)) {
                maxSize = i;
            }
            mImageLoader.displayImage(images.get(i).hyperlink, view,
                    mImageOptions);
            views.add(view);
            addView(view);
        }
        initListener();
    }
    private void initListener() {
        for (int i = 0; i < views.size(); i++) {
            View view = views.get(i);
            view.setTag(i);
            view.setOnTouchListener(this);
        }
    }

然后Activity 执行生命周期开始画组件的视图调用onMeasure方法画布局了,在这里面进行动态计算ViewgGroup宽高并记录下来

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        int resWidth = 0;
        int resHeight = 0;
        /**
         * 根据传入的参数,分别获取测量模式和测量值
         */
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int height = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        /**
         * 如果宽或者高的测量模式非精确值
         */
        if (widthMode != MeasureSpec.EXACTLY
                || heightMode != MeasureSpec.EXACTLY) {
            /**
             * 主要设置为背景图的高度
             */
            resWidth = getSuggestedMinimumWidth();
            /**
             * 如果未设置背景图片,则设置为屏幕宽高的默认值
             */
            resWidth = resWidth == 0 ? getDefaultWidth() : resWidth;

            resHeight = getSuggestedMinimumHeight();
            /**
             * 如果未设置背景图片,则设置为屏幕宽高的默认值
             */
            resHeight = resHeight == 0 ? getDefaultWidth() : resHeight;
        } else {
            /**
             * 如果都设置为精确值,则直接取小值;
             */
            resWidth = resHeight = Math.min(width, height);
        }

        setMeasuredDimension(resWidth, resHeight);
    }
    /**
     * 获得默认该layout的尺寸
     *
     * @return
     */
    private int getDefaultWidth() {
        WindowManager wm = (WindowManager) getContext().getSystemService(
                Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return Math.min(outMetrics.widthPixels, outMetrics.heightPixels);
    }

然后开始计算子控件的位置,并设置上。

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        /**
         * 获取父容器的宽度
         */
        int Width = getMeasuredWidth();
        /**
         * 容器的宽度/3分-item之间的间隙
         */
        ItemWidth = Width / 3 - padding - (padding / 3);
        System.out.println(l + "-" + t + "-" + r + "-" + b);
        for (int i = 0, size = getChildCount(); i < size; i++) {
            View view = getChildAt(i);
            if (i == 0) {
                mItmeOne = ItemWidth * 2 + padding;
                l += padding;
                t += padding;
                view.layout(l, t, l + mItmeOne, t + mItmeOne);
                l += mItmeOne + padding;
            }
            if (i == 1) {
                view.layout(l, t, l + ItemWidth, t + ItemWidth);
                t += ItemWidth + padding;
            }
            if (i == 2) {
                view.layout(l, t, l + ItemWidth, t + ItemWidth);
                t += ItemWidth + padding;
            }
            if (i >= 3) {
                view.layout(l, t, l + ItemWidth, t + ItemWidth);
                l -= ItemWidth + padding;
            }
            /**
             * 如果当前绘制的view与拖动的view的是一样则让其隐藏
             */
            if (i == hidePosition) {
                view.setVisibility(View.GONE);
                mStartDragItemView = view;
            }
        }
    }

好了原型布局是画好了,子控件的位置也是摆好了,就开始实现重头戏了,那就是触摸滑动了。

先是触摸缩小并移动到触摸的位置大家都值到关于触摸分发事件都会重写dispatchTouchEvent方法,如果对事件分发不熟悉的可以看guolin的《Android事件分发机制完全解析,带你从源码的角度彻底理解(上)》

http://blog.csdn.net/guolin_blog/article/details/9097463

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mHandler.removeCallbacks(mDragRunnable);

            mDownX = (int) ev.getX();
            mDownY = (int) ev.getY();
            mDragPosition = pointToPosition(mDownX, mDownY);
            /**
             * 判断获取的这个组件是否超出可以滑动的组件范围,如果超出了将分发事件
             */
            if (mDragPosition > maxSize) {
                return super.dispatchTouchEvent(ev);
            }
            /**
             * 判断触摸的组件是否符合范围,不符合 将分发事件
             */
            if (mDragPosition == -1) {
                return super.dispatchTouchEvent(ev);
            }
            /**
             * 根据position获取该item所对应的View
             */
            mStartDragItemView = getChildAt(mDragPosition);
            /**
             * 设置不销毁此View的cach
             */
            mStartDragItemView.setDrawingCacheEnabled(true);
            /**
             * 获取此View的BitMap对象
             */
            mDragBitmap = Bitmap.createBitmap(mStartDragItemView
                    .getDrawingCache());
            /**
             * 销毁cache
             */
            mStartDragItemView.destroyDrawingCache();

            dragPointX = mStartDragItemView.getWidth() / 2;
            dragPointY = mStartDragItemView.getHeight() / 2;
            dragOffsetX = (int) (ev.getRawX() - mDownX);
            dragOffsetY = (int) (ev.getRawY() - mDownY);
            /**
             * 将多线程加入消息队列并延迟50毫秒执行
             */
            mHandler.postDelayed(mDragRunnable, 50);
            break;
        case MotionEvent.ACTION_MOVE:
            moveX = (int) ev.getX();
            moveY = (int) ev.getY();
            if (mDragImageView != null) {
                onDragItem(moveX - dragPointX + dragOffsetX, moveY - dragPointY
                        + dragOffsetY - mTopHeight);
                onSwapItem(moveX, moveY);
            }
            break;
        case MotionEvent.ACTION_UP:
            onStopDrag();
            mHandler.removeCallbacks(mDragRunnable);
            break;
        case MotionEvent.ACTION_CANCEL:
            onStopDrag();
            mHandler.removeCallbacks(mDragRunnable);
            break;
        }
        return super.dispatchTouchEvent(ev);
    }

这里面是做了些什么事呢?首先获取你收触摸的位置判断是否触摸在了ViewGrou的组件子控件上,并获取当前时间,如果触摸上了就且抬起的时间较短,那么调用item点击回调方法,如果时间长则进行获取触摸上的那个控件一个缓存图片并赋值给一个ImageView的引用,并设置到GridLayout上,并且进行缩小位移动画。

 /**
     * 创建拖动的镜像
     *
     * @param bitmap
     * @param downX
     *            按下的点相对父控件的X坐标
     * @param downY
     *            按下的点相对父控件的X坐标
     */
    private void createDragImage() {
        int[] location = new int[2];
        mStartDragItemView.getLocationOnScreen(location);
        float drX = location[0];
        float drY = location[1] - mTopHeight;
        /**
         * 创建一个ImageView并将你点击的那一个item的Bitmap存进去
         */
        mDragImageView = new ImageView(getContext());
        mDragImageView.setImageBitmap(mDragBitmap);
        RootView.addView(mDragImageView);

        int drH = (int) (ItemWidth * 0.8);
        float w = mStartDragItemView.getWidth();
        final float scale = drH / w;
        createTranslationAnimations(mDragImageView, drX,
                mDownX - dragPointX + dragOffsetX, drY,
                mDownY - dragPointY + dragOffsetY - mTopHeight, scale, scale)
                .setDuration(200).start();
    }
    /**
     * 缩放动画加平移动画
     *
     * @param view
     *            将要执行动画的View组件
     * @param startX
     *            开始时的X坐标
     * @param endX
     *            结束时的X坐标
     * @param startY
     *            开始时的Y坐标
     * @param endY
     *            结束时的Y坐标
     * @param scaleX
     *            X轴的缩放比例
     * @param scaleY
     *            Y轴的缩放比列
     * @return 返回一个动画集合
     */
    private AnimatorSet createTranslationAnimations(View view, float startX,
            float endX, float startY, float endY, float scaleX, float scaleY) {
        AnimatorSet animSetXY = new AnimatorSet();
        animSetXY.playTogether(ObjectAnimator.ofPropertyValuesHolder(view,
                PropertyValuesHolder.ofFloat("translationX", startX, endX),
                PropertyValuesHolder.ofFloat("translationY", startY, endY),
                PropertyValuesHolder.ofFloat("scaleX", 1.0f, scaleX),
                PropertyValuesHolder.ofFloat("scaleY", 1.0f, scaleY)));
        return animSetXY;
    }

然后就是拖着镜像进行移动的动画了

    /**
     * 创建移动动画
     *
     * @param view
     *            动画执行的View
     * @param startX
     *            动画开始的X坐标
     * @param endX
     *            结束时的X坐标
     * @param startY
     *            开始时的Y坐标
     * @param endY
     *            结束时的Y坐标
     * @return 返回一个动画集合
     */
    private AnimatorSet createTranslationAnimations(View view, float startX,
            float endX, float startY, float endY) {
        System.out.println("pppppppppppppppppppppppppppppppppppppp");
        AnimatorSet animSetXY = new AnimatorSet();
        animSetXY.playTogether(ObjectAnimator.ofPropertyValuesHolder(view,
                PropertyValuesHolder.ofFloat("translationX", startX, endX),
                PropertyValuesHolder.ofFloat("translationY", startY, endY)));
        return animSetXY;
    }

移动的时候记录了他移动的位置并且判断是否到了其他子控件的位置上了。如果在他上面了那么就开始真个ViewGroup的子控件移动。

ps 这个就是重点了。这个AnimationSet实现了动画效果。

  /**
     * item的交换动画效果
     *
     * @param oldPosition
     *            正在拖拽的那一个View的编号
     * @param newPosition
     *            当前触摸到的那个组件的编号
     */
    public void animateReorder(int oldPosition, int newPosition) {
        /**
         * 判断触摸到的坐标的那一个View的编号是否大于现在正在拖拽的那一个坐标
         */
        boolean isForward = newPosition > oldPosition;
        final List<Animator> resultList = new LinkedList<Animator>();
        if (isForward) {
            for (int pos = oldPosition + 1; pos <= newPosition; pos++) {
                View view = getChildAt(pos);
                if (pos == 1) {
                    float h = view.getWidth() / 2;
                    float mSpacing = padding / 2;
                    float w = getChildAt(0).getWidth();
                    float scale = w / view.getWidth();
                    resultList.add(createTranslationAnimations(view, 0,
                            -(view.getWidth() + padding + mSpacing + h), 0, h
                                    + mSpacing, scale, scale));
                    swap(images, pos, pos - 1);
                }
                if (pos == 2) {
                    resultList.add(createTranslationAnimations(view, 0, 0, 0,
                            -(view.getWidth() + padding)));
                    swap(images, pos, pos - 1);
                }
                if (pos == 3) {
                    resultList.add(createTranslationAnimations(view, 0, 0, 0,
                            -(view.getWidth() + padding)));
                    swap(images, pos, pos - 1);
                }
                if (pos == 4) {
                    resultList.add(createTranslationAnimations(view, 0,
                            view.getWidth() + padding, 0, 0));
                    swap(images, pos, pos - 1);
                }
                if (pos == 5) {
                    resultList.add(createTranslationAnimations(view, 0,
                            view.getWidth() + padding, 0, 0));
                    swap(images, pos, pos - 1);
                }
            }
        } else {
            for (int pos = newPosition; pos < oldPosition; pos++) {
                View view = getChildAt(pos);
                if (pos == 0) {
                    float h = getChildAt(1).getWidth() / 2;
                    float mSpacing = padding / 2;
                    float w = getChildAt(0).getWidth();
                    float scale = getChildAt(1).getWidth() / w;
                    resultList.add(createTranslationAnimations(view, 0,
                            getChildAt(1).getWidth() + padding + mSpacing + h,
                            0, -(h + mSpacing), scale, scale));
                }
                if (pos == 1) {
                    resultList.add(createTranslationAnimations(view, 0, 0, 0,
                            view.getWidth() + padding));
                }
                if (pos == 2) {
                    resultList.add(createTranslationAnimations(view, 0, 0, 0,
                            view.getWidth() + padding));
                }
                if (pos == 3) {
                    resultList.add(createTranslationAnimations(view, 0,
                            -(view.getWidth() + padding), 0, 0));
                }
                if (pos == 4) {
                    resultList.add(createTranslationAnimations(view, 0,
                            -(view.getWidth() + padding), 0, 0));
                }
            }
            for (int i = oldPosition; i > newPosition; i--) {
                swap(images, i, i - 1);
            }
        }

        hidePosition = newPosition;
        resultSet = new AnimatorSet();
        /**
         * 给动画填充动画集
         */
        resultSet.playTogether(resultList);
        /**
         * 设置动画时间
         */
        resultSet.setDuration(150);
        /**
         * 设置其播放模式
         */
        resultSet.setInterpolator(new OvershootInterpolator(1.6f));
        resultSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                // TODO Auto-generated method stub
                mAnimationEnd = false;
            }

            @Override
            public void onAnimationEnd(Animator arg0) {
                // TODO Auto-generated method stub
                if (!mAnimationEnd) {
                    initUI();
                    resultSet.removeAllListeners();
                    resultSet.clone();
                    resultSet = null;
                    mDragPosition = hidePosition;
                }
                mAnimationEnd = true;
            }
        });
        resultSet.start();
        resultList.clear();
    }

ok整个流程就是这样的了。具体细节去看我给加了注释的源码(很详细的哦*^__\^* )。

感谢丶亲一口就跑的源码(http://www.apkbus.com/forum.php?mod=viewthread&tid=255164&highlight=%E6%8E%A2%E6%8E%A2

源码地址:http://download.csdn.net/download/bjp000111/9530934

时间: 2024-10-08 12:21:38

一个模仿探探头像编辑效果解析的相关文章

用C3中的animation和transform写的一个模仿加载的时动画效果

用用C3中的animation和transform写的一个模仿加载的时动画效果! 不多说直接上代码; html标签部分 <div class="wrap"> <h2>用C3中的animation和transform写的一个模仿加载的时动画效果</h2> <div class="demo"> <div></div> <div></div> <div></d

模仿京东顶部搜索条效果制作的一个小demo

最近模仿京东顶部搜索条效果制作的一个小demo,特贴到这里,今后如果有用到可以参考一下,代码如下 1 #define kScreenWidth [UIScreen mainScreen].bounds.size.width 2 #define kScreenHeight [UIScreen mainScreen].bounds.size.height 3 4 #import "mainViewController.h" 5 6 @interface mainViewController

做一个模仿Smarty的简易模版

---恢复内容开始--- 今天终于学习完了一个慕课网的正则表达式的视频,感觉挺不错的,推荐给大家 下面是我学习写的一些代码 首先要先明白什么是Smarty模版. 这种模版就是将一个文件中的代码运用正则替换为令一段代码以供下一步的运行.也就是说可以通过这个模版来省略很多语法,结构上面的规则,从而很好的将前端开发和后端开发分离,不需什么都懂,在这个山寨的模版里面主要是将前端的HTML代码中的一些字段替换成可被运行的PHP代码,完成给用户的最后的输出. 好,首先是新建的项目,既然有编译,就有个源文件和

Android弹幕功能实现,模仿斗鱼直播的弹幕效果

转载请注明出处:http://blog.csdn.net/sinyu890807/article/details/51933728 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每天都有文章更新. 大家好,感觉好像已经很久没更新博客了.前段时间主要是忙于新书的事情,时间比较紧张.而现在新书已经完稿,剩下的事情就都是出版社的工作了,那么我又可以抽出时间来写写博客了. 记得之前有位朋友在我的公众号里问过我,像直播的那种弹幕功能该如何实现?如今直播行业确实是非常火爆

Atom设置震撼的编辑效果

在代码编辑器.文本编辑器领域,有着不少的「神器」级的产品,如历史悠久的 VIM.Emacs 以及如今当红的SublimeText.另外还有 EditPlus.NotePad++.UltraEdit 等一大堆流行的利器,可谓百家争鸣. 然而,作为目前全球范围内影响力最大的代码仓库/开源社区,GitHub 的程序员们并不满足于此.他们使用目前最先进流行的技术重新打造了一款称为“属于21世纪”的代码编辑器——Atom, 它开源免费跨平台,并且整合 Git 并提供类似 SublimeText 的包管理功

模仿聊天窗口的分组的效果(粗糙的Demo)

#import "AViewController.h" #define max 8888888888 @interface AViewController ()<UITableViewDelegate,UITableViewDataSource> @property (nonatomic,strong)UITableView *myTabView; @property (nonatomic,strong)NSArray *dataArray; @property (nona

jsPlumb插件做一个模仿viso的可拖拉流程图

前言 这是我第一次写博客,心情还是有点小小的激动!这次主要分享的是用jsPlumb,做一个可以给用户自定义拖拉的流程图,并且可以序列化保存在服务器端. 我在这次的实现上面做得比较粗糙,还有分享我在做jsPlumb流程图遇到的一些问题. 准备工作 制作流程图用到的相关的脚本: 1 <script src="<%= ResolveUrl("~/resources/jquery/jquery-1.11.1.min.js")%>" type="t

一个面试题引起的SpringBoot启动解析

SpringBoot的故事从一个面试题开始 Spring Boot.Spring MVC 和 Spring 有什么区别? 分开描述各自的特征:Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等.但他们的基础都是Spring 的ioc和 aop,ioc 提供了依赖注入的容器, aop解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能. Spring MVC提供了一种轻度耦合的方式来开发web应用.它是Spring的一个模块,是一个web

Android 从无到有打造一个炫酷的进度条效果

从无到有打造一个炫酷的进度条效果