Android滚动选取金额

Posted 最美下雨天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android滚动选取金额相关的知识,希望对你有一定的参考价值。

UI效果图:


最终的效果是可以滑动刻度来选取金额,并且滑动停止后必须定位到某个金额上,不能停留在中间。

         分析:决定用listview来实现上述效果

                    分析UI图,发现有三种类型的item,短的,长的,还有长的带文字的。

1.listview所用的adapter的实现。

ListAdaptera.java文件

package com.miduo.financialmanageclient.ui.adapter;

import java.util.List;

import android.content.Context;
import android.content.ClipData.Item;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.miduo.financialmanageclient.R;

/**
 * 立即投资页面刻度 本来觉得不用复用了,结果发现会卡死,还是得复用
 * 
 * @author huozhenpeng
 * 
 */
public class ListAdaptera extends BaseAdapter {

	private Context context;
	private List<Integer> lists;
	private static final int TYPE_ITEM_FIRST = 0;
	private static final int TYPE_ITEM_SECOND = 1;
	private static final int TYPE_ITEM_THREE = 2;

	public ListAdaptera(Context context, List<Integer> lists) {
		this.context = context;
		this.lists = lists;
	}

	@Override
	public int getCount() {
		return lists.size();
	}

	@Override
	public Object getItem(int position) {
		return lists.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public int getItemViewType(int position) {
		if (position == 0 || position % 10 == 0) {
			return TYPE_ITEM_FIRST;
		} else if (position % 5 == 0) {
			return TYPE_ITEM_SECOND;
		} else {
			return TYPE_ITEM_THREE;
		}
	}

	@Override
	public int getViewTypeCount() {
		return 3;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder viewHolder = null;
		int type = getItemViewType(position);
		if (convertView == null) {
			switch (type) {
			case TYPE_ITEM_FIRST:
				viewHolder = new ViewHolder();
				convertView = LayoutInflater.from(context).inflate(
						R.layout.item_list, null);
				viewHolder.tv_left = ((TextView) convertView
						.findViewById(R.id.tv_left));
				viewHolder.tv_right = ((TextView) convertView
						.findViewById(R.id.tv_right));
				convertView.setTag(viewHolder);
				break;
			case TYPE_ITEM_SECOND:
				convertView = LayoutInflater.from(context).inflate(
						R.layout.item_list3, null);
				break;
			case TYPE_ITEM_THREE:
				convertView = LayoutInflater.from(context).inflate(
						R.layout.item_list2, null);
				break;
			default:
				break;
			}
		}
		switch (type) {
		case TYPE_ITEM_FIRST:
			viewHolder = (ViewHolder) convertView.getTag();
			viewHolder.tv_left.setText(lists.get(position) + "万");
			viewHolder.tv_right.setText(lists.get(position) + "万");
			break;
		case TYPE_ITEM_SECOND:
			break;
		case TYPE_ITEM_THREE:
			break;
		default:
			break;
		}

		return convertView;
	}

	final static class ViewHolder {
		TextView tv_left, tv_right;
	}

}

三个布局文件:

item_list.xml文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <View
        android:id="@+id/view1"
        android:layout_centerVertical="true"
        android:background="#999999"
        android:layout_width="@dimen/px2dp_28"
        android:layout_height="@dimen/px2dp_2" />
    <TextView 
        android:id="@+id/tv_left"
        android:layout_toRightOf="@id/view1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:text="10万"
        android:textColor="#999999"
        android:textSize="@dimen/px2sp_20"
        android:layout_marginLeft="@dimen/px2dp_6"
        />

    <View
        android:id="@+id/view2"
        android:layout_centerVertical="true"
        android:background="#999999"
        android:layout_width="@dimen/px2dp_28"
        android:layout_height="@dimen/px2dp_2"
        android:layout_alignParentRight="true" />
    <TextView 
        android:id="@+id/tv_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:text="10万"
        android:textColor="#999999"
        android:textSize="@dimen/px2sp_20"
        android:layout_toLeftOf="@id/view2"
        android:layout_marginRight="@dimen/px2dp_6"
        />

</RelativeLayout>


item_list2.xml文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:minHeight="@dimen/px2dp_26"
    android:layout_height="@dimen/px2dp_26" >

    <View
        android:layout_centerVertical="true"
        android:background="#999999"
        android:layout_width="@dimen/px2dp_18"
        android:layout_height="@dimen/px2dp_2" />

    <View
        android:layout_centerVertical="true"
        android:background="#999999"
        android:layout_width="@dimen/px2dp_18"
        android:layout_height="@dimen/px2dp_2"
        android:layout_alignParentRight="true" />

</RelativeLayout>

item_list3.xml文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:minHeight="@dimen/px2dp_26"
    android:layout_height="@dimen/px2dp_26" >

    <View
        android:id="@+id/view1"
        android:layout_width="@dimen/px2dp_28"
        android:layout_height="@dimen/px2dp_2"
        android:layout_centerVertical="true"
        android:background="#999999" />

    <View
        android:id="@+id/view2"
        android:layout_width="@dimen/px2dp_28"
        android:layout_height="@dimen/px2dp_2"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:background="#999999" />

</RelativeLayout>


注意:1.刚刚开始觉得布局文件比较简单,没有必要复用,后来发现如果计算出来的刻度特别多滑动又比较快会卡死。

           2.适配方式采用等比例适配。(就是说在720的手机上大小是72px的换到1080的手机上则占108px)。

先粘贴上全部代码,再对代码进行分析

MainActivityt.java类

package com.example.wavedemo;

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivityt extends Activity {

	private ListView listview;
	private List<Integer> lists = new ArrayList<Integer>();
	private ListAdaptera adapter;
	private int position;
	private int top;
	private int itemHeight;
	private int height;
	private int deltaItemNum;// 差距条数
	private int remainder;// 余数
	// 全部以万为单位
	private int startMoney = 5;// 起投金额
	private int deltaMoney = 1;// 递增金额
	private int canInvestMoney = 1097;// 可投金额
	// 补一个头部
	private LinearLayout ll_head;
	// 补一个footer
	private LinearLayout ll_footer;
	// 静止之后实际的position
	private int actualPosition;

	@TargetApi(19)
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_maint);
		itemHeight = (int) getResources().getDimension(R.dimen.px2dp_26);
		height = (int) getResources().getDimension(R.dimen.px2dp_544);
		initHead();
		initFooter();
		// 算出总共有多少个实际的格子(可以滑动到中间位置上的)
		for (int i = startMoney; i <= canInvestMoney; i += deltaMoney) {
			lists.add(i);
		}
		adapter = new ListAdaptera(this, lists);
		listview = (ListView) this.findViewById(R.id.listview);
		listview.addHeaderView(ll_head);
		listview.addFooterView(ll_footer);
		listview.setAdapter(adapter);
		listview.setOnItemSelectedListener(new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> parent, View view,
					int position, long id) {

			}

			@Override
			public void onNothingSelected(AdapterView<?> parent) {

			}
		});
		listview.setOnScrollListener(new OnScrollListener() {

			@SuppressLint("NewApi")
			@Override
			public void onScrollStateChanged(AbsListView view, int scrollState) {
				switch (scrollState) {
				case SCROLL_STATE_FLING:// 手指离开屏幕后,惯性滑动

					break;
				case SCROLL_STATE_IDLE:// 滑动后静止
					position = listview.getFirstVisiblePosition();// 第几个item
					top = getViewByPosition(position, listview).getTop();
					if (position == 0) {
						if (top == 0 || -top <= itemHeight / 2)// 定位到起投金额
						{
							listview.setSelectionFromTop(1,
									(height - itemHeight) / 2);
							actualPosition = 0;
						} else {
							listview.setSelectionFromTop(
									-(top + itemHeight / 2) / itemHeight + 2,
									(height - itemHeight) / 2);
							actualPosition = -(top + itemHeight / 2)
									/ itemHeight + 1;
						}
					} else {
						deltaItemNum = (height / 2 - (itemHeight + top))
								/ itemHeight;
						listview.setSelectionFromTop(position + deltaItemNum
								+ 1, (height - itemHeight) / 2);
						actualPosition = position + deltaItemNum;
					}
					MToast.showToast(MainActivityt.this,
							lists.get(actualPosition) + "万");
					showHighLight(actualPosition, listview);
					break;
				case SCROLL_STATE_TOUCH_SCROLL:// 手指在屏幕上滑动
					break;

				default:
					break;
				}

			}

			@Override
			public void onScroll(AbsListView view, int firstVisibleItem,
					int visibleItemCount, int totalItemCount) {

			}
		});
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		return true;
	}

	public View getViewByPosition(int pos, ListView listView) {
		final int firstListItemPosition = listView.getFirstVisiblePosition();
		final int lastListItemPosition = firstListItemPosition
				+ listView.getChildCount() - 1;

		if (pos < firstListItemPosition || pos > lastListItemPosition) {
			return listView.getAdapter().getView(pos, null, listView);
		} else {
			final int childIndex = pos - firstListItemPosition;
			return listView.getChildAt(childIndex);
		}
	}

	/**
	 * 添加辅助头部
	 */
	private void initHead() {
		ll_head = new LinearLayout(this);
		ll_head.setOrientation(LinearLayout.VERTICAL);
		AbsListView.LayoutParams params = new AbsListView.LayoutParams(
				AbsListView.LayoutParams.MATCH_PARENT,
				(height - itemHeight) / 2);
		ll_head.setLayoutParams(params);
		ll_head.addView(new View(this), 0, new LinearLayout.LayoutParams(
				LinearLayout.LayoutParams.MATCH_PARENT, itemHeight / 2));
		int total = (height - itemHeight) / 2 / itemHeight + 1;
		View view = null;
		for (int i = 1; i <= total; i++) {
			if (i % 5 == 0) {
				view = LayoutInflater.from(this).inflate(R.layout.item_list3,
						null);
			} else {
				view = LayoutInflater.from(this).inflate(R.layout.item_list2,
						null);
			}
			ll_head.addView(view, 0);
		}

	}

	/**
	 * 添加辅助头部
	 */
	private void initFooter() {
		ll_footer = new LinearLayout(this);
		ll_footer.setOrientation(LinearLayout.VERTICAL);
		AbsListView.LayoutParams params = new AbsListView.LayoutParams(
				AbsListView.LayoutParams.MATCH_PARENT,
				(height - itemHeight) / 2);
		ll_footer.setLayoutParams(params);
		ll_footer.addView(new View(this), 0, new LinearLayout.LayoutParams(
				LinearLayout.LayoutParams.MATCH_PARENT, itemHeight / 2));
		int total = (height - itemHeight) / 2 / itemHeight + 1;
		View view = null;
		for (int i = 1; i <= total; i++) {

			view = LayoutInflater.from(this).inflate(R.layout.item_list2, null);
			ll_footer.addView(view, 0);
		}

	}

	private void showHighLight(int pos, ListView listview) {
		View view = getViewByPosition(pos + 1, listview);
		TextView tv_left = (TextView) view.findViewById(R.id.tv_left);
		TextView tv_right = (TextView) view.findViewById(R.id.tv_right);
		if (tv_left != null) {
			tv_left.setTextColor(Color.parseColor("#fe7800"));
			tv_right.setTextColor(Color.parseColor("#fe7800"));
		}
	}

}

AndroidManifest.xml文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.wavedemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:hardwareAccelerated="false"
            android:name="com.example.wavedemo.MainActivityt"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.example.wavedemo.LoginActivity"></activity>
    </application>

</manifest>

运行效果:


代码分析:


定义listview每个item的高度

itemHeight = (int) getResources().getDimension(R.dimen.px2dp_26);
定义listview的总高度
height = (int) getResources().getDimension(R.dimen.px2dp_544);
初始化listview头部
initHead();
初始化listview脚部
initFooter();
为什么要给listview添加头部和脚部呢,

首先在刚刚进入页面的时候必须保证起投金额位于位于listview的正中间,此时listview可以向下滑动但是不能向上滑动,所以为了保证起投金额(5万,是adapter数据源(集合)中的第一个数据)位于listview正中间,需要给listview添加个头部,所以5万以上展现出来的效果其实是listview的一个头部。

代码中:

/**
	 * 添加辅助头部
	 */
	private void initHead() {
		ll_head = new LinearLayout(this);
		ll_head.setOrientation(LinearLayout.VERTICAL);
		AbsListView.LayoutParams params = new AbsListView.LayoutParams(
				AbsListView.LayoutParams.MATCH_PARENT,
				(height - itemHeight) / 2);
		ll_head.setLayoutParams(params);
		ll_head.addView(new View(this), 0, new LinearLayout.LayoutParams(
				LinearLayout.LayoutParams.MATCH_PARENT, itemHeight / 2));
		int total = (height - itemHeight) / 2 / itemHeight + 1;
		View view = null;
		for (int i = 1; i <= total; i++) {
			if (i % 5 == 0) {
				view = LayoutInflater.from(this).inflate(R.layout.item_list3,
						null);
			} else {
				view = LayoutInflater.from(this).inflate(R.layout.item_list2,
						null);
			}
			ll_head.addView(view, 0);
		}

	}

头部的计算稍微复杂一些,因为为了美观,5万的item是一个长的带刻度的,向上必须是四个小刻度再往上就是一个长的不带刻度的。

首先需要知道一个item的范围是多少:


             


青色框框住的部分是每个item的范围。相当于是每个刻度线在整个item内是居中显示的。

所以在计算头部高度的时候

AbsListView.LayoutParams params = new AbsListView.LayoutParams(
				AbsListView.LayoutParams.MATCH_PARENT,
				(height - itemHeight) / 2);



这里首先添加了半个item的高度,这半个item相当于是5万的那半个

ll_head.addView(new View(this), 0, new LinearLayout.LayoutParams(
				LinearLayout.LayoutParams.MATCH_PARENT, itemHeight / 2));

然后计算出剩余部分所需的item条数

int total = (height - itemHeight) / 2 / itemHeight + 1;

由于整形计算会舍弃精度,所以最后加上1



添加listview脚部也是相同的原理,为了能使最大额度(1097)滑到listview中间,所以需要加入脚部,脚部的代码比较简单,不做分析。


核心代码:

case SCROLL_STATE_IDLE:// 滑动后静止
					position = listview.getFirstVisiblePosition();// 第几个item
					top = getViewByPosition(position, listview).getTop();
					if (position == 0) {
						if (top == 0 || -top <= itemHeight / 2)// 定位到起投金额
						{
							listview.setSelectionFromTop(1,
									(height - itemHeight) / 2);
							actualPosition = 0;
						} else {
							listview.setSelectionFromTop(
									-(top + itemHeight / 2) / itemHeight + 2,
									(height - itemHeight) / 2);
							actualPosition = -(top + itemHeight / 2)
									/ itemHeight + 1;
						}
					} else {
						deltaItemNum = (height / 2 - (itemHeight + top))
								/ itemHeight;
						listview.setSelectionFromTop(position + deltaItemNum
								+ 1, (height - itemHeight) / 2);
						actualPosition = position + deltaItemNum;
					}
					MToast.showToast(MainActivityt.this,
							lists.get(actualPosition) + "万");
					showHighLight(actualPosition, listview);
					break;


position = listview.getFirstVisiblePosition();// 第几个item
这条代码比较简单,就是第一个可见的item,从0到很大


top = getViewByPosition(position, listview).getTop();
得到每个item的top值(Top position of this view relative to its parent.)


然后判断position是不是等于0,等于0说明起投金额(5万)还没有划出屏幕,这段代码的意思是应该滑动静止后应该定位到起投金额,

actualPosition是完全静止后实际的position,记录下来从集合中取数据用。

top==0是初始状态下,向下拖拽listview的情况

-top <= itemHeight / 2是向上拽,但是距离小于item一半的高度

setSelectionFromTop(int position,int y) 

@param position Index (starting at 0) of the data item to be selected.

@param y The distance from the top edge of the ListView (plus padding) that the

  item will be positioned.

也就是说position是要被选中的item,从0开始,y是距离被选中的item的高度,代码中写的1,是因为listview加了头部,头部算0位置。
(height - itemHeight) / 2)
是listview头部的高度,就是说第一个item距离顶部的距离为

(height - itemHeight) / 2


if (top == 0 || -top <= itemHeight / 2)// 定位到起投金额
						{
							listview.setSelectionFromTop(1,
									(height - itemHeight) / 2);
							actualPosition = 0;
						}


在向下滑动时,top一直为负值,并不断减小(-1,-2,-3,................)

-(top + itemHeight / 2)

这里的itemHeight/2是起投哪个item的下半截高度

 else {
							listview.setSelectionFromTop(
									-(top + itemHeight / 2) / itemHeight + 2,
									(height - itemHeight) / 2);
							actualPosition = -(top + itemHeight / 2)
									/ itemHeight + 1;
						}


---------------------------------分割线-------------------------------------------------分割线--------------------------------------------------分割线------------------------

分析另一种情况

这种情况下的top的绝对值肯定会小于itemHeight的值

else {
						deltaItemNum = (height / 2 - (itemHeight + top))
								/ itemHeight;
						listview.setSelectionFromTop(position + deltaItemNum
								+ 1, (height - itemHeight) / 2);
						actualPosition = position + deltaItemNum;
					}

这种情况是第一个可见的item不在是第一个item(不是起投的那个item)


--------------------------------------------------------------------------大分割线---------------------------------------------------------------------------------------

滑动停止后使用setSelectionFromTop定位比较生硬,改用smoothScrollToPositionFromTop,只替换方法即可。(需要post才能平滑定位)

类似代码:

listview.post(new Runnable() {
                                @Override
                                public void run() {
                                    listview.smoothScrollToPositionFromTop(position + deltaItemNum + 1, (height - itemHeight) / 2,500);
                                }
                            });


以上是关于Android滚动选取金额的主要内容,如果未能解决你的问题,请参考以下文章

js数字金额滚动动画(vue)

WPF 滚动选取框 - HandOffBehavior

SwiftUI 中的选取器委托滚动方法

android 如何去掉滚动侧边的那个线

以太坊系节点交易金额有效性判断

滚动到嵌套recyclerview android kotlin中的位置