Android使用ListView实现滚轮的动画效果实例

之前收到一个需求,需要把一个数据展示列表页面做成像滚轮那样的动画效果:中间最大然后向上下两端逐渐缩小。我想了想iOS那边自带滚轮组件,安卓得自己去实现,目前网上仿ios的滚轮组件的也有一些,但是感觉不适合我,我的要求没那么复杂,于是决定自己动手去实现一下。

动手前先分析一下应该怎么做,归根到底只是要实现缩放效果,由中间向两边变小,当一个item越接近中间就放大,越远离中间就缩小。那么可以通过先获取ListView的中点,然后获取当前可视的所有item跟ListView的中点的垂直距离计算出一个比例,然后将item的大小根据这个比例进行缩放,各个item跟ListView的中点的垂直距离不同,计算出来的比例也就不同,然后每次滚动的时候都计算比例然后进行缩放,这样应该就能实现我们想要的效果了。

因为一开始我的列表展示就是用ListView做的,有其他效果在里面,也不方便换其他组件,所以依然还是用ListView实现就好了。由于我们是每次一滚动都要进行缩放,ListView有提供一个OnScrollListener,它的onScroll方法每次一开始滚动就会调用,所以我们选择重写它。当它被调用的时候,我们就开始获取ListView中点,然后开始计算每个item的距离进行缩放.

/** * 中点的Y坐标 */ private float centerY = 0f; @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //计算中点 centerY = getHeight()/2; //判断中点的有效性 if(centerY <= 0){ return; } //开始对当前显示的View进行缩放 for(int i = 0; i < visibleItemCount; i++){ //获取item View temp_view = getChildAt(i); //计算item的中点Y坐标 float itemY = temp_view.getBottom()-(temp_view.getHeight()/2); //计算离中点的距离 float distance = centerY; if(itemY > centerY){ distance = itemY - centerY; }else{ distance = centerY - itemY; } //根据距离进行缩放 temp_view.setScaleY(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); temp_view.setScaleX(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); //根据距离改变透明度 temp_view.setAlpha(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); } }

后面不想加demo了,所以直接上整个ListView的代码吧

/** * 模仿滚轮动画缩放的ListView * Created by xu on 2017/3/3. */ public class XuListView extends ListView implements AbsListView.OnScrollListener { private static final String TAG = "XuListView"; /** * 中点的Y坐标 */ private float centerY = 0f; public XuListView(Context context, AttributeSet attrs) { super(context, attrs); //设置一个滚动监听 setOnScrollListener(this); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //计算中点 centerY = getHeight()/2; //判断中点的有效性 if(centerY <= 0){ return; } //开始对当前显示的View进行缩放 for(int i = 0; i < visibleItemCount; i++){ //获取item View temp_view = getChildAt(i); //计算item的中点Y坐标 float itemY = temp_view.getBottom()-(temp_view.getHeight()/2); //计算离中点的距离 float distance = centerY; if(itemY > centerY){ distance = itemY - centerY; }else{ distance = centerY - itemY; } //根据距离进行缩放 temp_view.setScaleY(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); temp_view.setScaleX(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); //根据距离改变透明度 temp_view.setAlpha(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); } } }

这样基本就实现了我们想要的效果了

但是现在有一个问题,就是第一个item和最后一个item无法滚动到中间从而放大突出显示。对此我暂时想了两个方法去解决:1、在头尾各种添加一些空白的item,使我们需要显示的数据都可以滚动到最中间。(我现在就是这么做的);2、使整个列表实现循环滚动。

添加空白的item的话,我是通过修改adapter去实现的。数据源是一个数组,我在数组前面和后面各加一些特殊的数据,adapter实现getview的时候,如果发现当前item的数据是特殊数据,那么item就变透明,从而实现了我们原本要显示的数据都可以被滚动最中间;

先从数据源下手,从头尾填充特殊的数据

public class MainActivity extends AppCompatActivity { XuListView mLisetview; MyAdapter adapter; ArrayList<String> nos = new ArrayList<String>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLisetview = (XuListView) findViewById(R.id.list_test); ArrayList<String> temp = new ArrayList<String>(); for(int i = 0;i<10;i++){ temp.add(i+""); } adapter = new MyAdapter(this,temp); mLisetview.setAdapter(adapter); resetitem(mLisetview); } /** * 在头尾填充透明的item数据 */ private void resetitem(ListView listview) { if(listview == null){ return; } //获取屏幕高度 WindowManager wm =getWindowManager(); int displayheight = wm.getDefaultDisplay().getHeight(); //计算一个item的高度 int itemhight = 0; if(adapter!=null){ View v=(View)adapter.getView(0, null, listview); v.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); itemhight=v.getMeasuredHeight(); } //根据Item的高度和屏幕的高度计算需要多少个item可以填满一半的屏幕 int newcount = ((displayheight/2)/itemhight); //填充前面的空白item for (int i = 1; i <= newcount; i++) { nos.add("full"); } //添加我们需要显示的数据 for(int i = 0;i<10;i++){ nos.add(i+""); } //填充后面的空白item for (int i = 1; i <= newcount; i++) { nos.add("full"); } //刷新数据 adapter.refreshData(nos); } }

然后adapter里面对头尾的特殊数据进行识别,将item变为透明的。

public class MyAdapter extends BaseAdapter { ArrayList<String> nos = new ArrayList<String>(); private Context context; public MyAdapter(Context context, ArrayList<String> nos){ this.context = context; this.nos = nos; } public void refreshData(ArrayList<String> nos) { this.nos = nos; notifyDataSetChanged(); } @Override public int getCount() { return nos.size(); } @Override public Object getItem(int position) { return nos.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { // 如果是第一次显示该页面(要记得保存到viewholder中供下次直接从缓存中调用) holder = new ViewHolder(); convertView = LayoutInflater.from(context).inflate(R.layout.item_test, null); holder.tv_no = (TextView) convertView.findViewById(R.id.tv_no); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tv_no.setText(nos.get(position)); if(nos.get(position).equals("full")){ convertView.setVisibility(View.INVISIBLE); }else{ convertView.setVisibility(View.VISIBLE); } return convertView; } private class ViewHolder { TextView tv_no; } }

这样我们就实现可以第一种解决方法

第二种方法,就是让整个ListView实现循环滚动,实现的方式有很多,大家可以自行百度,我这里就通过修改adapter的getCount方法去实现,就是在getCount方法return一个很大的值,getView获取数据的时候要模原本的数组大小,不然数组就越界了。然后ListView滚动到最中间,这样就实现伪循环滚动了

public class MainActivity extends AppCompatActivity { XuListView mLisetview; MyAdapter adapter; ArrayList<String> nos = new ArrayList<String>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLisetview = (XuListView) findViewById(R.id.list_test); ArrayList<String> temp = new ArrayList<String>(); for(int i = 0;i<10;i++){ temp.add(i+""); } adapter = new MyAdapter(this,temp); mLisetview.setAdapter(adapter); //滚动到中间 mLisetview.setSelection(adapter.getCount()/2); } } /** * Created by xu on 2017/6/27. */ public class MyAdapter extends BaseAdapter { ArrayList<String> nos = new ArrayList<String>(); private Context context; public MyAdapter(Context context, ArrayList<String> nos){ this.context = context; this.nos = nos; } @Override public int getCount() { return Integer.MAX_VALUE; } @Override public Object getItem(int position) { return nos.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { // 如果是第一次显示该页面(要记得保存到viewholder中供下次直接从缓存中调用) holder = new ViewHolder(); convertView = LayoutInflater.from(context).inflate(R.layout.item_test, null); holder.tv_no = (TextView) convertView.findViewById(R.id.tv_no); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tv_no.setText(nos.get(position%nos.size())); return convertView; } private class ViewHolder { TextView tv_no; } }

这样我们就实现了使列表进行循环滚动,从而达到每个item都可以滚动到中间突出显示的效果了

既然我们把动画效果都做出来了,那么也可以直接做成一个滚轮选择器,只需要加多两步:1、把最靠近中间的item滚动到中间;2、把中间的item的序号通过一个接口返回出去。 我就直接贴代码吧,反正也不难。

把最靠近中间的item滚动到中间的实现我是这么做的:首先决定好整个ListView可视的的item数量是多少,必须是奇数,这样才能只有一个item处于正中间,然后根据ListView的高度计算出每个item符合要求的高度,然后更改现有的item的高度,同时对内容进行缩放(不缩放内容单纯高度变小的话布局就乱了)。最后我们利用smoothScrollToPosition方法回正可视item中的第一个item,就实现了将最中间的item回滚到最中间的效果了。因为可视的item我们是根据ListView的高度去计算item的高度的,所有的可视item刚好铺满ListView,所以只要顶部那个回正了,其他的item也会回正。所以我们可以重写一下OnScrollListener的onScrollStateChanged方法,每次滚动结束就执行一次回滚

/** * 可视的item数 */ private int mVisibleItemCount = -1; /** * 没调整之前每个item的高度 */ private float olditemheight = 0; /** * 调整过后的每个item的高度 */ private float newitemheight = -1; /** * 调整每个可视的item的高度 以及对内容进行缩放 */ public void reSetItemHeight() { for (int i = 0; i < getChildCount(); i++) { //获取item View temp_view = getChildAt(i); //设置item的高度 ViewGroup.LayoutParams lp = temp_view.getLayoutParams(); lp.height = (int) newitemheight; temp_view.setLayoutParams(lp); //缩放内容 我的item的内容用一个LinearLayout包了起来 所以直接缩放LinearLayout LinearLayout item_ll_value = (LinearLayout) temp_view.findViewById(R.id.item_ll_value); item_ll_value.setScaleY((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); item_ll_value.setScaleX((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); } } /** * 计算在给定的可视item数目下 每个item应该设置的高度 * */ private void getNewItemHeight() { //先把旧的item存起来 olditemheight = getChildAt(0).getHeight(); //计算新的高度 newitemheight = getHeight() / mVisibleItemCount; if ((getHeight() / mVisibleItemCount) % newitemheight > 0) { //除不尽的情况下把余数分给各个item,暂时发现分一次余数就够了,如果效果不理想就做个递归多分几次 float remainder = (getHeight() / mVisibleItemCount) % newitemheight; newitemheight = remainder / mVisibleItemCount; } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //滚动结束之后开始回滚item if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && mVisibleItemCount != -1) { //使离中间最近的item回滚到中点位置 smoothScrollToPosition(getFirstVisiblePosition()); } }

实现了把最靠近中间的item滚动到中间,那么选择的item就是滚动结束后处于最中间的item。对此我们需要再次重写一下OnScrollListener的onScrollStateChanged方法。每次滚动结束后,取可视item中的第一个item的序号加上我们之前设置的可视item数的一半(舍去小数部分)就是最中间的item的序号了,也是当前选择项的序号。

/** * 当前选中项发生变化的监听者 */ private onSelectionChangeLisenter selectionChangeLisenter; /** * 设置选中项的监听者 */ public void setSelectionChangeLisenter(onSelectionChangeLisenter selectionChangeLisenter) { this.selectionChangeLisenter = selectionChangeLisenter; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //滚动结束之后开始正常回滚item并记录最中间的item为选中项 (必须设置可视项,ListView才会改为选择器模式) if( scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && mVisibleItemCount != -1){ //使离中间最近的item回滚到中点位置 smoothScrollToPosition(getFirstVisiblePosition()); //计算当前选中项的序号 int nowPosition = getFirstVisiblePosition() + mVisibleItemCount/2; //把当前选中项的序号存起来并通过listener回调出去 if(selectionChangeLisenter != null && nowPosition != curPosition){ curPosition = nowPosition; selectionChangeLisenter.onSelectionChange(curPosition); } } }

此处我是使用了一个接口去,用以实时把最新的数据返回出去

/** * Created by xu on 2017/3/3. */ public interface onSelectionChangeLisenter { void onSelectionChange(int position); }

使用这个滚轮选择器的方法也非常简单,除了跟正常的ListView初始化和绑定adapter之外,只需要额外调用两个方法就行了

//设置ListView的可视item数(必须是奇数) mLisetview.setVisibleItemCount(3); //设置监听者监听选中项的变化 mLisetview.setSelectionChangeLisenter(new onSelectionChangeLisenter() { @Override public void onSelectionChange(final int position) { mHandler.post(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this,"选择项发生变化 当前选中序号:"+(temp.get(position)),Toast.LENGTH_SHORT).show(); } }); } });

这样我们就实现滚轮数字选择器的效果了

现在贴下整个滚轮选择器的完整代码

/** * 模仿滚轮动画缩放的ListView * Created by xu on 2017/3/3. */ public class XuListView extends ListView implements AbsListView.OnScrollListener { private static final String TAG = "XuListView"; /** * 中点的Y坐标 */ private float centerY = 0f; /** * 可视的item数 */ private int mVisibleItemCount = -1; /** * 没调整之前每个item的高度 */ private float olditemheight = 0; /** * 调整过后的每个item的高度 */ private float newitemheight = -1; /** * 当前选中项发生变化的监听者 */ private onSelectionChangeLisenter selectionChangeLisenter; /** * 当前选中项的序号 */ private int curPosition = -1; public XuListView(Context context, AttributeSet attrs) { super(context, attrs); //设置一个滚动监听 setOnScrollListener(this); } /** * 设置选中项的监听者 */ public void setSelectionChangeLisenter(onSelectionChangeLisenter selectionChangeLisenter) { this.selectionChangeLisenter = selectionChangeLisenter; } /** * 设置ListView的显示item数 * @param count :必须是奇数 如果为-1 则表示只是使用动画效果的普通ListView */ public boolean setVisibleItemCount(int count){ if(count % 2 == 0){ return false; }else{ mVisibleItemCount = count; return true; } } /** * 在这里第一次调整item高度 */ @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if(mVisibleItemCount != -1){ getNewItemHeight(); reSetItemHeight(); } } /** * 调整每个可视的item的高度 以及对内容进行缩放 */ public void reSetItemHeight(){ for(int i = 0; i < getChildCount(); i++){ //获取item View temp_view = getChildAt(i); //设置item的高度 ViewGroup.LayoutParams lp = temp_view.getLayoutParams(); lp.height = (int)newitemheight; temp_view.setLayoutParams(lp); //缩放内容 我的item的内容用一个LinearLayout包了起来 所以直接缩放LinearLayout LinearLayout item_ll_value = (LinearLayout)temp_view.findViewById(R.id.item_ll_value); item_ll_value.setScaleY((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); item_ll_value.setScaleX((newitemheight / olditemheight) < 0 ? 0 : (newitemheight / olditemheight)); } } /** * 计算在给定的可视item数目下 每个item应该设置的高度 */ private void getNewItemHeight(){ //先把旧的item存起来 olditemheight = getChildAt(0).getHeight(); //计算新的高度 newitemheight = getHeight()/mVisibleItemCount; if((getHeight()/mVisibleItemCount) % newitemheight > 0){ //除不尽的情况下把余数分给各个item,暂时发现分一次余数就够了,如果效果不理想就做个递归多分几次 float remainder = (getHeight()/mVisibleItemCount) % newitemheight; newitemheight = remainder/mVisibleItemCount; } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //滚动结束之后开始正常回滚item并记录最中间的item为选中项 (必须设置可视项,ListView才会改为选择器模式) if( scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && mVisibleItemCount != -1){ //使离中间最近的item回滚到中点位置 smoothScrollToPosition(getFirstVisiblePosition()); //计算当前选中项的序号 int nowPosition = getFirstVisiblePosition() + mVisibleItemCount/2; //把当前选中项的序号存起来并通过listener回调出去 if(selectionChangeLisenter != null && nowPosition != curPosition){ curPosition = nowPosition; selectionChangeLisenter.onSelectionChange(curPosition); } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //计算中点 centerY = getHeight()/2; //判断中点的有效性 if(centerY <= 0){ return; } //开始对当前显示的View进行缩放 for(int i = 0; i < visibleItemCount; i++){ //获取item View temp_view = getChildAt(i); //计算item的中点Y坐标 float itemY = temp_view.getBottom()-(temp_view.getHeight()/2); //计算离中点的距离 float distance = centerY; if(itemY > centerY){ distance = itemY - centerY; }else{ distance = centerY - itemY; } //根据距离进行缩放 temp_view.setScaleY(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); temp_view.setScaleX(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); //根据距离改变透明度 temp_view.setAlpha(1.1f - (distance / centerY) < 0 ? 0 : 1.1f - (distance / centerY)); } } }

注释很详细 相信小白看了也没什么难度。

滚轮效果的实现方式有很多,解决头尾两个item无法滚动到中间的方法也很多,我说的方法仅供参考,没有最好的方法,只有最符合自己的需求的方法。

demo下载地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-10-27 23:18:55

Android使用ListView实现滚轮的动画效果实例的相关文章

Android直播app送礼物连击动画效果(实例代码)

最近在做公司的直播项目,需要实现一个观看端连击送礼物的控件: 直接上代码: /** * @author yangyinglong on 2017/7/11 16:52. * @Description: todo(这里用一句话描述这个类的作用) * @Copyright Copyright (c) 2017 Tuandai Inc. All Rights Reserved. */ public class CustomGiftView extends LinearLayout { private

Android xml实现animation的4种动画效果实例代码_Android

animation有四种动画类型:分别为alpha(透明的渐变).rotate(旋转).scale(尺寸伸缩).translate(移动),二实现的分发有两种,一种是javaCode,另外一种是XML,而我今天要说的是XML实现的方法,个人感觉javaCode的实现方法比xml要简单,所以有需要的可以自己去找找资料看看. 先给大家展示下效果图,如果大家感觉还不错,请继续往下阅读. 下面是我的四个xml文件,分别代表这四种动画类型. alpha.xml COde: <?xml version=&quo

Android开发中MJRefresh自定义刷新动画效果_Android

[一]常见用法 最原始的用法,耦合度低,但是不能统一管理.我们需要在每一个控制器都写以下代码,很繁琐,以后项目修改起来更繁琐,得一个控制器一个控制器的去定位.修改. 1.1 使用默认刷新(耦合度底,但是想统一修改起来特别麻烦) self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ //在这里执行刷新操作 }]; self.tableView.mj_header = [MJRefreshNorm

Android实现左右摆动的球体动画效果_Android

首先,看一下效果   可能各位在别处看到过类似的东西,我在微信的文章末尾看到有个玩意,感觉有意思,就用代码实现一下.这篇文章主要把握写代码的思路展示一下. 看到上图,我想各位能想到最简单的实现方案就是用动画,切很多图出来,然后就可以轻松实现了.为了不让自己再舒适区里呆的太安逸,就弄点麻烦的:通过计算来实现.文章的末尾会将全部代码贴出,复制可以直接运行. 需要回忆的知识 重力势能 E = mgh 动能 E = ½mv² 在理想状态下,动能和重力式能可以相互转换,且能量守恒 如果不想太注意细节,以上

Android xml实现animation的4种动画效果实例代码

animation有四种动画类型:分别为alpha(透明的渐变).rotate(旋转).scale(尺寸伸缩).translate(移动),二实现的分发有两种,一种是javaCode,另外一种是XML,而我今天要说的是XML实现的方法,个人感觉javaCode的实现方法比xml要简单,所以有需要的可以自己去找找资料看看. 先给大家展示下效果图,如果大家感觉还不错,请继续往下阅读. 下面是我的四个xml文件,分别代表这四种动画类型. alpha.xml COde: <?xml version=&quo

Android开发中MJRefresh自定义刷新动画效果

[一]常见用法 最原始的用法,耦合度低,但是不能统一管理.我们需要在每一个控制器都写以下代码,很繁琐,以后项目修改起来更繁琐,得一个控制器一个控制器的去定位.修改. 1.1 使用默认刷新(耦合度底,但是想统一修改起来特别麻烦) self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ //在这里执行刷新操作 }]; self.tableView.mj_header = [MJRefreshNorm

android通过子线程来实现动画的实例

android通过子线程来实现动画的实例 Android动画,一般是相对原始位置进行参照,本文我们来看看通过子线程修改物体位置实现动画的实例. 布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="

Android 仿今日头条简单的刷新效果实例代码_Android

点击按钮,先自动进行下拉刷新,也可以手动刷新,刷新完后,最后就多一行数据.有四个选项卡. 前两天导师要求做一个给本科学生预定机房座位的app,出发点来自这里.做着做着遇到很多问题,都解决了.这个效果感觉还不错,整理一下. MainActivity package com.example.fragmentmytest; import android.content.DialogInterface; import android.graphics.Color; import android.os.B

Android 仿今日头条简单的刷新效果实例代码

点击按钮,先自动进行下拉刷新,也可以手动刷新,刷新完后,最后就多一行数据.有四个选项卡. 前两天导师要求做一个给本科学生预定机房座位的app,出发点来自这里.做着做着遇到很多问题,都解决了.这个效果感觉还不错,整理一下. MainActivity package com.example.fragmentmytest; import android.content.DialogInterface; import android.graphics.Color; import android.os.B