最近爱上写博客了,一来可以巩固自己的学习成果,二来可以帮助有需要的人。好了闲话就到这里,开始进入今天的正题。
这篇博客是上篇博客点击打开链接的后续,上篇博客跟大家一起时间了仿大街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<T> { private Context mContext; private List<T> mDatas; private int mCurrentIndex;//目前数据集读到的下标 public StackLayoutAdapter(Context context, List<T> 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<User>(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<View> 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<User>(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; } });