一个模仿探探头像编辑效果解析
Posted bjp_ftz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个模仿探探头像编辑效果解析相关的知识,希望对你有一定的参考价值。
此前一直在做模仿一个探探头像编辑的效果,但是水平不够一直没做出来,最后看到了丶亲一口就跑的源码
(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
以上是关于一个模仿探探头像编辑效果解析的主要内容,如果未能解决你的问题,请参考以下文章
Android基础控件——PopupWindow模仿ios底部弹窗
JavaScript练习---[JS动态切换图片效果;JS完成简易计算器, 下拉框切换头像, JS 制作简易文本编辑器]