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

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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
无论你是一个程序猿还是一个程序媛,每天干的事除了coding还是coding,代码不能解决的问题(什么问题自己心里还没点abcd数嘛),探探能帮你解决。最近网上特流行一款交友软件叫探探(据说是yp软件)。作为探探曾经的一名从来只浏览图片但是没有yue过的资深玩家同时又是一位热爱前端的妹子,我决定要仿一下这个app。既然是寄几开发,那还不是寄几说了算,毫无疑问整款APP的主题风格被我改成我最爱的终极少女粉了hhh,下面让我们一起来感受下探探的魅力吧~项目整体效果项目部分功能点解析主页图片左滑右滑对应按钮变化首先我们来聊一下最让我头痛的地方,就是主页面的左右滑动事件并且对应按钮会相应变化,即我左滑一下图片下面的灰色按钮会有相应的动画效果,右滑则对应着图片下面的红色按钮。对于一个刚入小程序坑的妹子来说,没有大神指点不知道要在这里面的逻辑坑还要绕多久才能绕出来。得一高人指点,我才完美滴实现了这个功能。这里写了三个大的盒子放着图片和文字信息,再将他们放到swiper-item里面,用swiper组件实现整个盒子的左右滑动                                          K             ♂21             金牛座             文化/教育                哦盒子下面不是按钮,我是放了两张图片。             先给他们写个滑动的时候触发的动画效果.active {    animation: active 1s ease;//定义一个叫做active的动画} @keyframes active {//补充active动作脚本     0% {        transform: scale(0.8);     }     50% {        transform: scale(1.2);     }     100% {        transform: scale(1.0);     } }在page的data里面定义三个变量,将left,right变量绑定到对应图片中data: {        left: false ,       right: false,        activeIndex: 0 },在swiper绑定事件中具体判断左右滑动事件changeswiper: function(e) {         var index = e.detail.current;//当前所在页面的 index     if(index > this.data.activeIndex) {//左滑事件判断       this.setData({         left: true//若为左滑,left值为true,触发图片动画效果       })     } else if(index  {//每滑动一次,数据发生变化       this.setData({         activeIndex: index,         left:false,         right:false       })     }, 1000);   },从本地上传图片这个呀查一查小程序开发文档就好了,先在要上传图片的地方的src绑定个数据变量放入图片默认地址,就是上传图片之前的添加图片data: {     imgUrl: '../../images/addImg.png'   },通过绑定tap事件将上传的图片地址替换进去uploadImg: function(e) { var that = this; wx.chooseImage({   count: 1, //上传图片数量   sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有   sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有   success: function (res) {// 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片       var tempFilePaths = res.tempFilePaths;       that.setData({           imgUrl: tempFilePaths     })       wx.showToast({//显示上传成功           title: '上传成功',           icon: 'success',           duration: 2000     })   } }),配对成功列表据通过easy-mock获取后台数据block wx:for渲染一个包含多节点的结构块                                                                                                                         {{item.nickname}}                     {{item.message}}                                            获取后台数据wx.request({       url: 'https://www.easy-mock.com/mock/5a23dbf382614c0dc1bebf04/getFriendsList/getFriendsList',       success: (res) => {         // console.log(response);         this.setData({           friendsList: res.data.data.friendsList         })       }     })其它差不多就是切页面了,个人原因用不太习惯weui的官方样式,每个页面都是我自己呕心沥血码出来的,所以大家不喜轻点喷哈,还在努力学习当中~~~项目开发用到的一些工具微信开发者工具、VScode、GithubIconfont阿里巴巴矢量图标库:各种图片logo应有尽有,前端开发必备esay-mock:模拟数据请求,实现无后端编程W3Cschool微信小程序开发教程手册文档:开发小程序要多看看哦小结emmmm目前项目功能还是很简单呀,还有很多功能后面慢慢实现吧~比如利用将上传的图片放到storage中,页面刷新之后图片依然在,slider滑动到某一处在页面上保存当前值,模拟配对成功后弹出提醒页面等等......也希望遇到热爱学习的小伙伴一起交流学习,一起在前端坑里越陷越深hhh项目地址:https://github.com/beautifulg... 求鼓励~求star呀~我的邮箱:[email protected] 这里可以找到我哦作者:略略略

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值