最近,额,不对,挺久之前,有一类交友APP,喜欢的右划,不喜欢的左划。看到的时候感觉还挺新鲜的,就琢磨着自己整一波。下面上图,那阴影应该是录制软件的关系,连鼠标印子都上去了。
然后这个控件叫LikeOrNotView(真是佩服自己取出这么接地气的名字=。=)
然后用法。
<com.linyuzai.likeornot.LikeOrNotView xmlns:like_or_not="http://schemas.android.com/apk/res-auto"
android:id="@+id/like_or_not"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#eeeeee"
like_or_not:animator_duration="300"
like_or_not:drag_scale="0.35"
like_or_not:move_multiplier="3"
like_or_not:rotation_direction="clockwise"
like_or_not:rotation_range="5"
like_or_not:style_rotatable="true"
like_or_not:style_stratified="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/activity_horizontal_margin"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/back"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="40dp"
android:layout_marginTop="20dp"
android:scaleType="centerInside"
android:src="@mipmap/back" />
<ImageView
android:id="@+id/nope"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="40dp"
android:scaleType="centerInside"
android:src="@mipmap/nope" />
<ImageView
android:id="@+id/like"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="40dp"
android:scaleType="centerInside"
android:src="@mipmap/like" />
<ImageView
android:id="@+id/star"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="20dp"
android:scaleType="centerInside"
android:src="@mipmap/star" />
</LinearLayout>
</com.linyuzai.likeornot.LikeOrNotView>
里面需要一个子控件,就是图中下面四个按钮的那块。如果不需要那块东西,就随便放个view上去,因为我默认是有一个子view。说明一下那个,自定义的属性
Attrs | Introduce |
---|---|
style_rotatable | 拖动的时候可旋转 |
style_stratified | 是否有层次效果 |
rotation_direction | 旋转方向 默认右划顺时针 |
rotation_range | 旋转范围 会乘一定比例,默认5 |
animator_duration | 非手动拖动时动画持续时间 默认300ms |
drag_scale | 设置有效滑动距离 为屏幕宽度*drag_scale,默认0.35 |
move_multiplier | 松手后view滑动距离和用手滑动距离的倍数 说简单点,当你发现手一甩,view没甩出屏幕 就把这个参数加大 |
然后设置一下Adapter(和Listener)
mLikeOrNotView.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return 10;
}
@Override
public View getView(View convertView, ViewGroup parent, final int position) {
if (convertView == null)
convertView=LayoutInflater.from(parent.getContext()).inflate(R.layout.item_like_or_not, parent, false);
TextView textView = (TextView) convertView.findViewById(R.id.text);
textView.setText(position + "");
return convertView;
}
});
mLikeOrNotView.setOnItemClickListener(new LikeOrNotView.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
}
});
mLikeOrNotView.setOnLikeOrNotListener(new LikeOrNotView.OnLikeOrNotListener() {
@Override
public void onLike(View view, int position) {
}
@Override
public void onNope(View view, int position) {
}
@Override
public void onAnimationEnd() {
}
});
mLikeOrNotView.setViewStateCallback(new LikeOrNotView.ViewStateCallback() {
@Override
public void onViewReleased(View view) {
}
@Override
public void onViewPositionChanged(View view, int left, int top, float scale) {
}
});
主要就是用ViewDragHelper和ObjectAnimator,说实话一直觉得ViewDragHelper有bug,所以平时都喜欢直接写onTouchEvent,虽然ViewDragHelper确实很方便,然后就讲一下思路,和一些坑(如果我还记得,省略部分代码。。。)
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int mOffsetY = 0;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
mOffsetY = (int) ev.getY();
break;
}
return mOffsetY > mLimitBottomY ? super.onInterceptTouchEvent(ev) : mDragHelper.shouldInterceptTouchEvent(ev);
}
mLimitBottomY可滑动范围的最大Y值,超过了就交给父类的onInterceptTouchEvent方法,没超过就交给ViewDragHelper的shouldInterceptTouchEvent方法。另外,当你的view可以点击时,在onTouchEvent里面拿不到MotionEvent.ACTION_DOWN,所以在onInterceptTouchEvent里面那手指按下的坐标。
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (Math.abs(mDownX - mMoveX) < mLimitDragDistance) {
//滑动距离不够,最终位置为初始位置
mFinalPosition.x = mInitialPosition.x;
mFinalPosition.y = mInitialPosition.y;
} else {
//最终的位置为手滑动的距离*mMoveMultiplier
mFinalPosition.x = mInitialPosition.x + (int) (mMoveX - mDownX) * mMoveMultiplier;
mFinalPosition.y = mInitialPosition.y + (int) (mMoveY - mDownY) * mMoveMultiplier;
//如果有层次,将下面两张按比例还原
if (isStratified)
overlapAfterReleased();
}
mDragHelper.settleCapturedViewAt(mFinalPosition.x, mFinalPosition.y);
invalidate();
}
手松开时,滑动距离不够的话(滑动距离可以用drag_scale调整,默认屏幕宽度的0.35)回到原来的位置,要不然就把view甩出去,坐标为手按下的坐标减掉手松开的坐标乘一个倍数(可用move_multiplier设置)
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
float scale = (left - mInitialPosition.x) / mLimitDragDistance;
//如果可旋转,旋转一定角度
if (mRotatable) {
float mRotation = 0f;
switch (mRotationDirection) {
case ANTICLOCKWISE:
mRotation = -mRotationRange;
break;
case CLOCKWISE:
mRotation = mRotationRange;
break;
}
//旋转
changedView.setRotation(mRotation * scale);
}
//返回原处或被甩出去
if (left == mFinalPosition.x && top == mFinalPosition.y) {
//设置为没有旋转角度
changedView.setRotation(0f);
//被甩出去
if (mReleasedView != null && left != mInitialPosition.x && top != mInitialPosition.y) {
mReleasedView = null;
//当前的position+1
mCurrentPosition++;
removeView(changedView);
//在最后载入一个view,如果需要有层次,进行层次的动画
setStratifiedAfterLoadView(loadView(changedView));
}
}
}
如果要旋转,就按照某个比例旋转,当位置和甩出的位置一样,就把这个view给remove,然后在最后添加一个view。这边讲一下view的回收,我的思路是这样的,先加载5个view,然后每次划走一个,就把这个划走的view当做convertView传回去,加载再后面的那一个,形成一个循环。如果view都加载完了,多出来的view存到一个list里面,以备后用。然后是层次效果
private void initOverlap() {
View child0 = getChildAt(0);
View child1 = getChildAt(getChildCount() - 2);
View child2 = getChildAt(getChildCount() - 3);
if (child1 != null && child1 != child0) {
child1.setScaleX(1f - mScale);
child1.setScaleY(1f - mScale);
child1.setTranslationY(mOffsetY);
}
if (child2 != null && child2 != child0) {
child2.setScaleX(1f - 2f * mScale);
child2.setScaleY(1f - 2f * mScale);
child2.setTranslationY(2f * mOffsetY);
}
if (getChildCount() > 4) {
for (int index = 1; index < getChildCount() - 3; index++) {
View child = getChildAt(index);
if (child != null) {
child.setScaleX(1f - 2f * mScale);
child.setScaleY(1f - 2f * mScale);
child.setTranslationY(2f * mOffsetY);
}
}
}
}
这是层次效果的初始化,拿到最顶上第二个view和的三个view,通过setScale,setTranslation进行变换(就是从ObjectAnimator受到的启发,记得变换完了以后,不管是用动画还是直接设置,都需要再手动设置回来)
其实这样讲下来,好像也没多么坎坷,不过其实里面还是有有一些bug我还是弄不太明白,我之所以一开始加载5个view而不是3个就是因为那个bug。嗯,差不多就是这样,想到其他的再补。