利用Recycleview水平平移并自动挪动Item位置(仿Instagram效果)

先来看看Instagram在编辑图片水平挪动的过程中的效果图

这个效果分两个,一个是水平滑动的效果,另一个是当我点击靠边的某个选项的时候,若这个选项与它靠近的边之间还隔着一个没有完全显示的选项,会自动平移位置,将这个没有完全显示的选项显示出来,或者当点击的选项就是最边上的选项,它也会自动移动将它之前(或者之后)的选项移动出来。

知道效果后,我们就要用代码实现这个效果,按照庖丁解牛一步步达到我们的功能。

首先第一步是做一个能水平滑动的view,最早之前android只提供了竖直滑动的ListView(大家都很熟悉吧),这两年又提供了一个Recycleview可以自由控制水平还是竖直滚动,同时它的适配器的写法也比ListView更简单些,Recylerview用法网上的例子很多了,大家自己百度下。我们的这个demo就是自定义一个继承Recycleview的View,在本身就有水平滚动功能的基础上加入自动平移的功能。所以写贴出关键代码的自定义View的代码

package com.jc.demo.recylerview;

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.Scroller;

public class AutoAdjustRecylerView extends RecyclerView {
	private final String TAG = "AutoAdjustRecylerView";
	private Scroller mScroller = null;
	private int mLastx = 0;
	//用于设置自动平移时候的速度
	private float mPxPerMillsec = 0;
	private AutoAdjustItemClickListener mListener = null;

	public AutoAdjustRecylerView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		initData(context);
	}

	public AutoAdjustRecylerView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		initData(context);
	}

	public AutoAdjustRecylerView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
		initData(context);
	}

	private void initData(Context context){
		mScroller = new Scroller(context, new Interpolator() {
			public float getInterpolation(float t) {
			      return t;
			  }
		});
	}

	public void setScroller(Scroller scroller){
		if(mScroller != scroller){
			mScroller = scroller;
		}
	}

	@Override
	public void computeScroll() {
		// TODO Auto-generated method stub
		super.computeScroll();
		if(mScroller != null){
			if (mScroller.computeScrollOffset())//如果mScroller没有调用startScroll,这里将会返回false。
		    {
		        Log.d(TAG, "getCurrX = " +  mScroller.getCurrX());
		        scrollBy(mLastx - mScroller.getCurrX(), 0);
		        mLastx = mScroller.getCurrX();
		        //继续让系统重绘
		        postInvalidate();
		     }
		}
	}

	public AutoAdjustItemClickListener getItemClickListener() {
		return mListener;
	}

	public void setItemClickListener(AutoAdjustItemClickListener listener) {
		this.mListener = listener;
	}

	public float getPxPerMillsec() {
		return mPxPerMillsec;
	}

	public void setPxPerMillsec(float pxPerMillsec) {
		this.mPxPerMillsec = pxPerMillsec;
	}

	public void checkAutoAdjust(int position){
		int childcount = getChildCount();
		//获取可视范围内的选项的头尾位置
		int firstvisiableposition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
		int lastvisiableposition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
		Log.d(TAG, "childcount:" + childcount + " position:"+ position +" firstvisiableposition:" + firstvisiableposition
				+ " lastvisiableposition" + lastvisiableposition);
		if(position == (firstvisiableposition + 1) || position == firstvisiableposition){
			//当前位置需要向右平移
			leftScrollBy(position, firstvisiableposition);
		}
		else if(position == (lastvisiableposition - 1) || position == lastvisiableposition){
			//当前位置需要向做平移
			rightScrollBy(position, lastvisiableposition);
		}
	}

	private void leftScrollBy(int position, int firstvisiableposition){
		View leftChild = getChildAt(0);
		if(leftChild != null){
			int leftx = leftChild.getLeft();
			Log.d(TAG, "leftChild left:" + leftx);
			int startleft = leftx;
			int endleft = position == firstvisiableposition?leftChild.getWidth():0;
			Log.d(TAG, "startleft:" + startleft + " endleft" + endleft);
			autoAdjustScroll(startleft, endleft);
		}
	}

	private void rightScrollBy(int position, int lastvisiableposition){
		int childcount = getChildCount();
		View rightChild = getChildAt(childcount - 1);
		if(rightChild != null){
			int rightx = rightChild.getRight();
			int dx = rightx - getWidth();
			Log.d(TAG, "rightChild right:" + rightx + " dx:" + dx);
			int startright = dx;
			int endright = position == lastvisiableposition?-1 * rightChild.getWidth():0;
			Log.d(TAG,"startright:" + startright + " endright:" + endright);
			autoAdjustScroll(startright, endright);
		}
	}

	/**
	 *
	 * @param start 滑动起始位置
	 * @param end 滑动结束位置
	 */
	private void autoAdjustScroll(int start, int end){
		int duration = 0;
        if(mPxPerMillsec != 0){
        	duration = (int)((Math.abs(end - start)/mPxPerMillsec));
        }
        Log.d(TAG, "duration:" + duration);
		mLastx = start;
		mScroller.startScroll(start, 0, end - start, 0, duration);
		postInvalidate();
	}

	public abstract class AbstractAutoAdjustViewHolder extends ViewHolder implements OnClickListener{

		private final static String TAG = "AutoAdjustViewHolder";

		public AbstractAutoAdjustViewHolder(View view) {
			super(view);
			view.setOnClickListener(this);
			initView(view);
		}

		protected abstract void initView(View view);

		/**
		 * 点击监听
		 */
		@Override
		public void onClick(View v) {
			//单击选项的时候判断是否需要移动
			checkAutoAdjust(getPosition());
			if(mListener != null){
				mListener.onItemClick(v,getPosition());
			}
		}
	}
}

首先我们看这个自定义View代码最底下有个AbstractAutoAdjustViewHolder抽象类,看到ViewHolder大家应该都知道它的作用啥吧,由于Recycleview没有ItemClick事件,因此我们必须自己添加了,所以有了这个抽象类,用来给每个Item的View设置OnClick事件,然后在这个OnClick事件里去判断是否需要平移以及将click事件回调出去,这里有一个接口用于回调点击事件

package com.jc.demo.recylerview;

import android.view.View;

public interface AutoAdjustItemClickListener {
	public void onItemClick(View view,int postion);
}

当我们点击了Item的时候,就触发了checkAutoAdjust方法去判断当前点击点的位置在整个可视范围所处的位置,代码91和95行分别判断了点击位置是否处于最左边,临近最左边或者最右边,临近最右边,然后计算好要平移的起始和结束位置,并最终在133行的autoAdjustScroll方法里进行滑动。滑动的方式用比较常用的Scroller的方式,当我们调用了Scroller的startScroller方法后postInvalidate重绘整个view,重绘过程会调用到computeScroll()方法(代码53行),然后在这个方法里取出scroller帮我们计算出来的平移值去做平移,就达到了我们看到的自动平移的效果。

整个核心的类差不多就是这样了,现在贴出UI部分的三个类,一个是ViewHolder继承了我们刚刚的AbstractAutoAdjustViewHolder

package com.jc.demo;

import android.view.View;
import android.widget.ImageView;

import com.jc.demo.recylerview.AutoAdjustRecylerView;
import com.jc.demo.recylerview.AutoAdjustRecylerView.AbstractAutoAdjustViewHolder;

public class TestViewHolder extends AbstractAutoAdjustViewHolder{

	public ImageView mImageView;

	public TestViewHolder(AutoAdjustRecylerView autoAdjustRecylerView, View view) {
		autoAdjustRecylerView.super(view);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void initView(View view) {
		// TODO Auto-generated method stub
		mImageView = (ImageView) view.findViewById(R.id.item_iv);
	}

}

里面的Item布局为

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:padding="10.0dip"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/item_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
       />

</LinearLayout>

要使用自定义view我们得做个适配器Adapter

package com.jc.demo;

import com.jc.demo.recylerview.AutoAdjustRecylerView;

import android.support.v7.widget.RecyclerView.Adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class TestAdapter extends Adapter<TestViewHolder>{

	private int[] mDatas;
	private AutoAdjustRecylerView mAutoAdjustRecylerView = null;

    public TestAdapter(AutoAdjustRecylerView autoAdjustRecylerView, int[] datas) {
        this.mAutoAdjustRecylerView = autoAdjustRecylerView;
    	mDatas = datas;
    }

    @Override
    public int getItemCount() {
        return mDatas.length;
    }

	@Override
	public void onBindViewHolder(TestViewHolder holder, int position) {
		// TODO Auto-generated method stub
		holder.mImageView.setImageResource(mDatas[position]);
	}

	@Override
	public TestViewHolder onCreateViewHolder(ViewGroup parent, int arg1) {
		// TODO Auto-generated method stub
		View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
        TestViewHolder testViewHolder = new TestViewHolder(mAutoAdjustRecylerView, itemView);
		return testViewHolder;
	}

}

然后就是主界面和主界面布局

package com.jc.demo;

import com.jc.demo.recylerview.AutoAdjustItemClickListener;
import com.jc.demo.recylerview.AutoAdjustRecylerView;

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.util.Log;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class AutoAdjustActivity extends Activity implements AutoAdjustItemClickListener{
	private final String TAG = "AutoAdjustActivity";
	private AutoAdjustRecylerView mRecyclerView = null;
	final int[] mIds = {R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d,
	        R.drawable.e, R.drawable.f, R.drawable.g, R.drawable.h,
	        R.drawable.i, R.drawable.j, R.drawable.k, R.drawable.l,
	        R.drawable.m, R.drawable.n, R.drawable.o, R.drawable.p,
	        R.drawable.q};
	final int[] mIdsUpper = {R.drawable.aa, R.drawable.bb, R.drawable.cc, R.drawable.dd,
	        R.drawable.ee, R.drawable.ff, R.drawable.gg, R.drawable.hh,
	        R.drawable.ii, R.drawable.jj, R.drawable.kk, R.drawable.ll,
	        R.drawable.mm, R.drawable.nn, R.drawable.oo, R.drawable.pp,
	        R.drawable.qq};
	private TextView mTvText = null;
	private ImageView mImageView1 = null;
	private ImageView mImageView2 = null;
	private ImageView mImageView3 = null;
	private ImageView mImageView4 = null;
	private ImageView mImageView5 = null;
	private int mClickPosition = 0;
	private ScaleAnimation mScaleAni = null;
	private RotateAnimation mRotateAni = null;
	private AnimationSet mAnimationSet = null;
	private AlphaAnimation mAlphaAnimation = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvText = (TextView)findViewById(R.id.tv_text);
        mImageView1 = (ImageView)findViewById(R.id.iv_pic1);
        mImageView2 = (ImageView)findViewById(R.id.iv_pic2);
        mImageView3 = (ImageView)findViewById(R.id.iv_pic3);
        mImageView4 = (ImageView)findViewById(R.id.iv_pic4);
        mImageView5 = (ImageView)findViewById(R.id.iv_pic5);
        mRecyclerView = (AutoAdjustRecylerView) findViewById(R.id.recyclerView);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayout.HORIZONTAL);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mRecyclerView.setItemClickListener(this);
        mRecyclerView.setPxPerMillsec(0.3f);
        TestAdapter adapter = new TestAdapter(mRecyclerView, mIds);
        mRecyclerView.setAdapter(adapter);
        initData();
    }

    private void initData(){
    	mScaleAni = new ScaleAnimation(1.0f, 0.5f, 1.0f, 0.5f,50,50); //Scale Animation
		mScaleAni.setRepeatCount(0);
		mScaleAni.setRepeatMode(Animation.REVERSE);  

		mRotateAni = new RotateAnimation(0, 180, 50, 50); //Rotate Animation
		mRotateAni.setRepeatMode(Animation.REVERSE);
		mRotateAni.setStartOffset(150);
		mAnimationSet = new AnimationSet(false);   //Animation Set
		mAnimationSet.addAnimation(mScaleAni);
		mAnimationSet.addAnimation(mRotateAni);
		mAnimationSet.setAnimationListener(new AnimationListener() {

			@Override
			public void onAnimationStart(Animation arg0) {
				Log.d(TAG, "onAnimationStart");
			}

			@Override
			public void onAnimationEnd(Animation arg0) {
				mImageView1.setBackgroundResource(mIds[mClickPosition]);
				mImageView2.setBackgroundResource(mIds[mClickPosition]);
				mImageView3.setBackgroundResource(mIds[mClickPosition]);
				mImageView4.setBackgroundResource(mIds[mClickPosition]);
				mImageView5.setBackgroundResource(mIds[mClickPosition]);
			}

			@Override
			public void onAnimationRepeat(Animation arg0) {
			}
		});
		mAnimationSet.setDuration(300);
		mAlphaAnimation = new AlphaAnimation(1.0f, 0f);
		mAlphaAnimation.setAnimationListener(new AnimationListener() {

			@Override
			public void onAnimationStart(Animation arg0) {

			}

			@Override
			public void onAnimationRepeat(Animation arg0) {

			}

			@Override
			public void onAnimationEnd(Animation arg0) {
				mTvText.setText("TEST" + (mClickPosition + 1));
			}
		});
		mAlphaAnimation.setDuration(1000);
    }

	@Override
	public void onItemClick(View view, final int position) {
		Log.d(TAG, "position:" + position);
		mClickPosition = position;
		mAnimationSet.setStartOffset(0);
		mImageView1.startAnimation(mAnimationSet);
		mImageView2.startAnimation(mAnimationSet);
		mImageView3.startAnimation(mAnimationSet);
		mImageView4.startAnimation(mAnimationSet);
		mImageView5.startAnimation(mAnimationSet);
		mAlphaAnimation.setStartOffset(0);
		mTvText.startAnimation(mAlphaAnimation);
	}

}
<LinearLayout 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:orientation="vertical"
    android:background="#e5e5e6"
     >
    <LinearLayout
          android:layout_width="match_parent"
		  android:layout_height="wrap_content"
		  android:orientation="vertical"
		        >
		    <TextView
		        android:id="@+id/tv_text"
		        android:layout_width="wrap_content"
			    android:layout_height="wrap_content"
			    android:text="TEST1"
			    android:layout_gravity="center_horizontal"
			    android:textSize="24sp"
			    android:textColor="@android:color/black"
		        />
		   <ImageView
			    android:id="@+id/iv_pic1"
			    android:layout_width="wrap_content"
			    android:layout_height="wrap_content"
			    android:background="@drawable/a"
			    android:layout_gravity="center_horizontal"
			    />
		   <ImageView
			    android:id="@+id/iv_pic2"
			    android:layout_width="wrap_content"
			    android:layout_height="wrap_content"
			    android:background="@drawable/a"
			    android:layout_gravity="center_horizontal"
			    />
		    <ImageView
			    android:id="@+id/iv_pic3"
			    android:layout_width="wrap_content"
			    android:layout_height="wrap_content"
			    android:background="@drawable/a"
			    android:layout_gravity="center_horizontal"
			    />
		     <ImageView
			    android:id="@+id/iv_pic4"
			    android:layout_width="wrap_content"
			    android:layout_height="wrap_content"
			    android:background="@drawable/a"
			    android:layout_gravity="center_horizontal"
			    />
		     <ImageView
			    android:id="@+id/iv_pic5"
			    android:layout_width="wrap_content"
			    android:layout_height="wrap_content"
			    android:background="@drawable/a"
			    android:layout_gravity="center_horizontal"
			    />
    </LinearLayout>
    <com.jc.demo.recylerview.AutoAdjustRecylerView
        android:id="@+id/recyclerView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</LinearLayout>

Demo源码位置

时间: 2024-07-29 14:11:48

利用Recycleview水平平移并自动挪动Item位置(仿Instagram效果)的相关文章

li 水平排列并自动填满 ul

找了li 如何水平排列并自动填满 ul,同时 li 宽度平均?资料,里面有提到"请用js动态计算保证兼容性", 因为我想实现的是,水平滚动条,ul的上级div是固定的宽度1000px, 我就想到用jquery 获取每个li的高度,ul的宽度等于每个li的宽度. 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DT

web 工程中利用Spring的 ApplicationContextAware接口自动注入bean

最常用的办法就是用 ClassPathXmlApplicationContext, FileSystemClassPathXmlApplicationContext, FileSystemXmlApplicationContext 等对象去加载Spring配置文件,这样做也是可以, 但是在加载Spring配置文件的时候,就会生成一个新的ApplicaitonContext对象而不是Spring容器帮我们生成的哪一个, 这样就产生了冗余, 所以不采用应用程序手动加载文件的方式,而是使用Applic

1.利用consul实现k8s服务自动发现

标题 : 1.利用consul实现k8s服务自动发现 目录 : 微服务架构设计 序号 : 1 ] } } ] } } ? - consul自身支持ACL,但目前,Helm图表不支持其中一些功能,需要额外的手动配置, 有关详细信息可参阅:https://www.consul.io/docs/platform/k8s/helm.html - 我们使用basic-auth作了授权,当用户访问consul-ui时需要提供用户名和密码 ?shell yum -y install httpd echo "$

GitHub Android 最火开源项目Top20 GitHub 上的开源项目不胜枚举,越来越多的开源项目正在迁移到GitHub平台上。基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要。利用这些项目,有时能够让你达到事半功倍的效果。

1. ActionBarSherlock(推荐) ActionBarSherlock应该算得上是GitHub上最火的Android开源项目了,它是一个独立的库,通过一个API和主题,开发者就可以很方便地使用所有版本的Android动作栏的设计模式. 对于Android 4.0及更高版本,ActionBarSherlock可以自动使用本地ActionBar实现,而对于之前没有ActionBar功能的版本,基于Ice Cream Sandwich的自定义动作栏实现将自动围绕布局.能够让开发者轻松开发

可以拖动交换item位置的GridView

欢迎关注Android技术分享公众号(小红人). 这篇文章是基于夏安明写的一个可以移动item的Demo改写的,因为原代码有一些BUG,比如adapter不能使用ViewHolder优化(这个问题应该是最大的问题)再比如不能使用上下拉刷新功能(这个是我额外添加的功能,不知道的可以去看看开源中国手机客户端的便签功能) 在Android开发中,我们常常用到ListView和GridView,而有的时候系统的ListView,GridView并不能满足我们的需求,所以我们需要自己定义一个ListVie

利用target的特性,可以实现纯css的tab效果切换

基础知识: :target起作用的是href连接到的位置 如 <a href="#tab1">tab1</a> <div id="tab1" class="tab">This is a tab1</div> :target{ color:red; } 但点击a标签的时候,连接到id是tab1的div,对这个div起作用color:red; 如: <a href="#tab"

微信朋友圈评论时被评论状态Item位置计算参考

mListView.setSelectionFromTop(position, y); 利用上述方法设置Item在ListView中的位置.y为Item距离ListView顶端的距离,也就是图中的1: 计算方式:1 = 2 - 3 -4 坐标计算: int[] location = new int[2]; view.getLocationOnScreen(location); view为需要计算坐标的控件:location[0] : x轴坐标 location[1] : y轴坐标 这里计算高度只

利用POI获取Excel中图片和图片位置

利用POI获取Excel中图片和图片位置(支持excel2003or2007多sheet) 转自:http://blog.csdn.net/delongcpp/article/details/8833995 第三方JAR包(apache下载POI即可): poi-3.9-20121203.jar dom4j-1.6.1.jar poi-ooxml-3.9-20121203.jar poi-ooxml-schemas-3.9-20121203.jar poi-scratchpad-3.9-2012

利用.dSYM跟.app文件准确定位Crash位置

本文转载至  http://blog.csdn.net/lvxiangan/article/details/28102629 利用.dSYM和.app文件准确定位Crash位置首先,确保在release(Ad Hoc或者App Store)一个版本时,保存了对应的xxx.app和xxx.dSYM文件. 其次,验证xxx.crash.xxx.app和xxx.dSYM三者的uuid是否一致. 验证方法: 1)查看xxx.app的uuid. [plain] view plaincopy $ dwarf