最近爱上写博客了,一来可以巩固自己的学习成果,二来可以帮助有需要的人。好了闲话就到这里,开始进入今天的正题。
这篇博客是上篇博客点击打开链接的后续,上篇博客跟大家一起时间了仿大街app的拖动删除或者收藏的效果,如果还没看的话,可以去看看。当然没看的话也不影响看这篇博客。为了给不看上篇博客的人提供方便,我在这里先贴下今天我们要完成的效果。
还是那句话,感兴趣的看官可以看下,顺便帮我顶下,谢谢啦。不感兴趣的大腿请绕道呀。好了,开工!
private StackLayoutAdapter mAdapter;
private int mContentWidth = 350;//内容区域的宽度 dp
private int mContentHeight = 470;//内容区域的高度 dp
private float mRotateFactor;//控制item旋转范围
private double mItemAlphaFactor;//控制item透明度变化范围
private int mLimitTranslateX = 100;//限制移动距离,当超过这个距离的时候,删除该item
public StackLayout(Context context)
{
this(context, null);
}
public StackLayout(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public StackLayout(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.StackLayout);
mContentWidth = t.getDimensionPixelSize(R.styleable.StackLayout_contentWidth, ScreenUtils
.dp2px(mContentWidth, getContext()));
mContentHeight = t.getDimensionPixelSize(R.styleable.StackLayout_contentHeight,
ScreenUtils.dp2px(mContentHeight, getContext()));
int screenWidth = ScreenUtils.getScreenWidth(getContext());
mRotateFactor = 60 * 1.0f / screenWidth;
//左滑,透明度最少到0.1f
mItemAlphaFactor = 0.9 * 1.0f / screenWidth / 2;
}
public abstract class StackLayoutAdapter
{
private Context mContext;
private List mDatas;
private int mCurrentIndex;//目前数据集读到的下标
public StackLayoutAdapter(Context context, List datas)
{
this.mContext = context;
this.mDatas = datas;
}
public int getCount()
{
return mDatas.size();
}
public T getItem(int pos)
{
return mDatas.get(pos);
}
public int getCurrentIndex()
{
return mCurrentIndex;
}
public void setCurrentIndex(int index)
{
this.mCurrentIndex = index;
}
public abstract View getView(int pos, View convertView, ViewGroup parent);
public void setAdapter(StackLayoutAdapter adapter)
{
this.mAdapter = adapter;
//最多加载两条数据
int itemCount = adapter.getCount();
int loadCount = itemCount > 2 ? 2 : itemCount;
for (int i = 0; i < loadCount; i++)
{
addViewToFirst();
}
}
/**
* 将item添加到最后的位置
*/
public void addViewToFirst()
{
makeAndAddView(0);
}
private void makeAndAddView(int pos)
{
if (mAdapter.getCurrentIndex() == mAdapter.getCount() - 1)
{
return;//没有更多数据
}
View item = obtainView(mAdapter.getCurrentIndex());
addView(item, pos);
//增加数据集的下标
mAdapter.setCurrentIndex(mAdapter.getCurrentIndex() + 1);
}
首先先判断数据当前下标是否已经到数据的末尾了,如果到了直接返回,否则,调用obtainView方法获得一个view,获得view后将view添加到StackLayout中指定的位置去,这里根据上下文,传入的是0这个位置,也就是布局的第一个位置。添加后更新下数据集的下标。
private View obtainView(int pos)
{
//加载布局
View item = LayoutInflater.from(getContext()).inflate(R.layout.stack_item, null);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mContentWidth, mContentHeight,
Gravity
.CENTER_HORIZONTAL);
item.setLayoutParams(lp);
item = mAdapter.getView(pos, item, this);
//初始化事件
initEvent(item);
return item;
}
跟上篇博客差不都,还是调用的inflate方法来加载一个布局,然后设置一下它的参数。嗯,这里看下mAdapter.getView方法,这是一个抽象方法,当你在为StackLayout设置Adapter的时候是需要重写这个方法的。
private void initEvent(final View item)
{
//设置item的重心,主要是旋转的中心
item.setPivotX(item.getLayoutParams().width / 2);
item.setPivotY(item.getLayoutParams().height * 2);
item.setOnTouchListener(new View.OnTouchListener()
{
float touchX, distanceX;//手指按下时的坐标以及手指在屏幕移动的距离
@Override
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
touchX = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
distanceX = event.getRawX() - touchX;
item.setRotation(distanceX * mRotateFactor);
//alpha scale 1~0.1
//item的透明度为从1到0.1
item.setAlpha(1 - (float) Math.abs(mItemAlphaFactor * distanceX));
break;
case MotionEvent.ACTION_UP:
if (Math.abs(distanceX) > mLimitTranslateX)
{
//移除view
removeViewWithAnim(getChildCount() - 1, distanceX < 0);
addViewToFirst();
} else
{
//复位
item.setRotation(0);
item.setAlpha(1);
}
break;
}
return true;
}
});
}
public View removeViewWithAnim(int pos, boolean isLeft)
{
final View view = getChildAt(pos);
view.animate()
.alpha(0)
.rotation(isLeft ? -90 : 90)
.setDuration(400).setListener(new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator animation)
{
removeView(view);
if (getChildCount() == 0)//如果只剩一条item的时候
{
Toast.makeText(getContext(), "已是最后一页...", Toast.LENGTH_SHORT).show();
}
}
});
return view;
}
为了不让用户显得突兀,我们这里要有一个淡出的效果,当然要根据左滑还是右滑设置淡出的方向,然后在动画结束的时候在removeView,并且如果没有更多数据的时候,给用户打印个toast。
private StackLayout mContainer;
private double mItemIvAlphaFactor;//控制item上面的图片的透明度变化范围
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContainer = (StackLayout) findViewById(R.id.flContainer);
mContainer.setAdapter(new StackLayoutAdapter(this, GenerateData.getDatas())
{
@Override
public View getView(int pos, View convertView, ViewGroup parent)
{
if (convertView != null)
{
User user = getItem(pos);
// convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout
// .stack_item, null);
ImageView roundAvatar = (ImageView) convertView.findViewById(R.id.roundAvatar);
ImageView blurAvatar = (ImageView) convertView.findViewById(R.id.blurAvatar);
CatImageLoader.getInstance().loadImage(user.getAvater(), blurAvatar);
CatImageLoader.getInstance().loadImage(user.getAvater(), roundAvatar);
TextView tvUsername = (TextView) convertView.findViewById(R.id.tvUsername);
TextView tvSchool = (TextView) convertView.findViewById(R.id.tvSchool);
TextView tvMajor = (TextView) convertView.findViewById(R.id.tvMajor);
TextView tvEntranceTime = (TextView) convertView.findViewById(R.id
.tvEntranceTime);
TextView tvSkill = (TextView) convertView.findViewById(R.id.tvSkill);
final ImageView ivIgnore = (ImageView) convertView.findViewById(R.id.ivIgnore);
final ImageView ivInterested = (ImageView) convertView.findViewById(R.id
.ivInterested);
tvUsername.setText(user.getName());
tvSchool.setText(user.getSchool());
tvMajor.setText(user.getMajor() + " | " + user.getSchoolLevel());
tvEntranceTime.setText(user.getEntranceTime());
tvSkill.setText("装逼 吹牛逼");
}
return convertView;
}
});
}
这种类型的代码是不是炒鸡眼熟?跟listview很像吧?不过这个是我们自己实现的,是不是有点小激动,反正作为一个小鸟,我觉得是有点小激动。ok,来看看我们目前为止的效果。
public interface onTouchEffectListener
{
void onTouchEffect(View item, MotionEvent event, float distanceX);
}
public void setOnTouchEffectListener(onTouchEffectListener listener)
{
this.mOnTouchEffectListener = listener;
}
private void initEvent(final View item)
{
//设置item的重心,主要是旋转的中心
item.setPivotX(item.getLayoutParams().width / 2);
item.setPivotY(item.getLayoutParams().height * 2);
item.setOnTouchListener(new View.OnTouchListener()
{
float touchX, distanceX;//手指按下时的坐标以及手指在屏幕移动的距离
@Override
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
touchX = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
distanceX = event.getRawX() - touchX;
if (mOnTouchEffectListener != null)
mOnTouchEffectListener.onTouchEffect(item, event, distanceX);
item.setRotation(distanceX * mRotateFactor);
//alpha scale 1~0.1
//item的透明度为从1到0.1
item.setAlpha(1 - (float) Math.abs(mItemAlphaFactor * distanceX));
break;
case MotionEvent.ACTION_UP:
if (mOnTouchEffectListener != null)
mOnTouchEffectListener.onTouchEffect(item, event, distanceX);
if (Math.abs(distanceX) > mLimitTranslateX)
{
//移除view
removeViewWithAnim(getChildCount() - 1, distanceX < 0);
addViewToFirst();
} else
{
//复位
item.setRotation(0);
item.setAlpha(1);
}
break;
}
return true;
}
});
}
mContainer.setOnTouchEffectListener(new StackLayout.onTouchEffectListener()
{
@Override
public void onTouchEffect(View item, MotionEvent event, float distanceX)
{
ImageView ivIgnore = (ImageView) item.findViewById(R.id.ivIgnore);
ImageView ivInterested = (ImageView) item.findViewById(R.id
.ivInterested);
switch (event.getAction())
{
case MotionEvent.ACTION_MOVE:
if (distanceX < 0)//如果为左滑
{
//显示忽略图标,隐藏感兴趣图标
ivIgnore.setVisibility(View.VISIBLE);
ivInterested.setVisibility(View.GONE);
ivIgnore.setAlpha((float) (Math.abs(distanceX) * mItemIvAlphaFactor));
} else//如果为右滑
{
//显示感兴趣图标,隐藏忽略图标
ivIgnore.setVisibility(View.GONE);
ivInterested.setVisibility(View.VISIBLE);
ivInterested.setAlpha((float) (distanceX * mItemIvAlphaFactor));
}
break;
case MotionEvent.ACTION_UP:
if (Math.abs(distanceX) < mContainer.getLimitTranslateX())
{
//复位
ivIgnore.setAlpha(1.0f);
ivInterested.setAlpha(1.0f);
ivIgnore.setVisibility(View.GONE);
ivInterested.setVisibility(View.GONE);
}
break;
}
}
});
前面的代码就不贴了,就贴下增加的代码,相信不用过多的解释了吧。注释也有。然后看一下效果。
private LinkedList mScrapViews = new LinkedList<>();
首先不用想,我们肯定得有个容器来存放我们废弃掉的view,这里我使用LinkedList。接下来哪里需要有复用view的逻辑呢?答案显而易见,肯定是在remoeView的时候将view添加到废弃list中,addView的时候从废弃list中获取view了。那么看看removeViewWithAnim的新版本
public View removeViewWithAnim(final View view, boolean isLeft)
{
// final View view = getChildAt(pos);
view.animate()
.alpha(0)
.rotation(isLeft ? -90 : 90)
.setDuration(400).setListener(new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator animation)
{
removeView(view);
//移除view后将view添加到我们的废弃view的list中
resetItem(view);//记得重置状态,否则复用的时候会看不到view
mScrapViews.add(view);
if (getChildCount() == 0)//如果只剩一条item的时候
{
Toast.makeText(getContext(), "已是最后一页...", Toast.LENGTH_SHORT).show();
}
}
});
return view;
}
没什么改变,就是在removeView后,重置一下状态。这一步很重要,因为view此时的透明度为0,是看不见的,如果不重置,将会导致你复用view的时候看不到。接着就把view添加到废弃list中了。我们看一眼resetItem
private void resetItem(View item)
{
item.setRotation(0);
item.setAlpha(1);
}
不多说了。接着看我们添加view时的逻辑
private View obtainView(int pos)
{
//先尝试从废弃缓存中取出view
View scrapView = mScrapViews.size() > 0 ? mScrapViews.removeLast() : null;
View item = mAdapter.getView(pos, scrapView, this);
if (item != scrapView)
{
//代表view布局变化了,inflate了新的布局
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mContentWidth,
mContentHeight, Gravity.CENTER_HORIZONTAL);
item.setLayoutParams(lp);
//初始化事件
initEvent(item);
}
return item;
}
public static class ViewHolder
{
public ImageView roundAvatar;
public ImageView blurAvatar;
public TextView tvUsername;
public TextView tvSchool;
public TextView tvMajor;
public TextView tvEntranceTime;
public TextView tvSkill;
public ImageView ivIgnore;
public ImageView ivInterested;
public ViewHolder(ImageView roundAvatar, ImageView blurAvatar, TextView tvUsername,
TextView tvSchool, TextView tvMajor, TextView tvEntranceTime, TextView
tvSkill, ImageView ivIgnore, ImageView ivInterested)
{
this.roundAvatar = roundAvatar;
this.blurAvatar = blurAvatar;
this.tvUsername = tvUsername;
this.tvSchool = tvSchool;
this.tvMajor = tvMajor;
this.tvEntranceTime = tvEntranceTime;
this.tvSkill = tvSkill;
this.ivIgnore = ivIgnore;
this.ivInterested = ivInterested;
}
}
mContainer.setAdapter(new StackLayoutAdapter(this, GenerateData.getDatas())
{
@Override
public View getView(int pos, View convertView, ViewGroup parent)
{
ViewHolder viewHolder;
User user = getItem(pos);
if (convertView == null)
{
convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout
.stack_item, null);
ImageView roundAvatar = (ImageView) convertView.findViewById(R.id.roundAvatar);
ImageView blurAvatar = (ImageView) convertView.findViewById(R.id.blurAvatar);
TextView tvUsername = (TextView) convertView.findViewById(R.id.tvUsername);
TextView tvSchool = (TextView) convertView.findViewById(R.id.tvSchool);
TextView tvMajor = (TextView) convertView.findViewById(R.id.tvMajor);
TextView tvEntranceTime = (TextView) convertView.findViewById(R.id
.tvEntranceTime);
TextView tvSkill = (TextView) convertView.findViewById(R.id.tvSkill);
ImageView ivIgnore = (ImageView) convertView.findViewById(R.id.ivIgnore);
ImageView ivInterested = (ImageView) convertView.findViewById(R.id
.ivInterested);
viewHolder = new ViewHolder(roundAvatar, blurAvatar, tvUsername, tvSchool,
tvMajor, tvEntranceTime, tvSkill, ivIgnore, ivInterested);
convertView.setTag(viewHolder);
} else
{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.ivIgnore.setVisibility(View.GONE);
viewHolder.ivInterested.setVisibility(View.GONE);
CatImageLoader.getInstance().loadImage(user.getAvater(), viewHolder.blurAvatar);
CatImageLoader.getInstance().loadImage(user.getAvater(), viewHolder.roundAvatar);
viewHolder.tvUsername.setText(user.getName());
viewHolder.tvSchool.setText(user.getSchool());
viewHolder.tvMajor.setText(user.getMajor() + " | " + user.getSchoolLevel());
viewHolder.tvEntranceTime.setText(user.getEntranceTime());
viewHolder.tvSkill.setText("装逼 吹牛逼");
return convertView;
}
});