android滚轮选择器详细教程

最近在学习自定义android控件,碰上了滚动选择器的效果,自己找来了别人代码,一眼看上去晕头转向(因为缺少经验),第二天静下心来,才能透过现象看本质,觉得写一篇博文帮助其他同学。

下面先看效果图

sss

何为本质,就是思路,我一开始看到这个就懵了,没有思路,后来看了别人的代码,才明白是怎么一回事,凭着自己的理解,居然非常顺利的就写出来了。

思路:1,布局,整个控件的布局,其实就是用代码取带xml来实现当前布局

2,可以滑动的(即滚轮),其实是一个ScrollView

3,判断滑动状态的,有protected void onScrollChanged(int x, int y, int oldx, int oldy) 方法,可以为我们获得当前y值(一开始y=0;随着滑动,y开始增大)

 

那么我们首先来完成第一个,为了思考方便,我先用xml搭建出了控件的样子,然后我们再用代码去实现,事实证明,这样的思路行云流水

下面,我们来看这个test.xml

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.androidtest.MainActivity" >

    <RelativeLayout
        android:id="@+id/relativeLayout1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/act_menu_bg_hover" >
		 <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginTop="100dp"
        android:background="@drawable/shoukuan_border4" />
        
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="300dp"
            android:orientation="horizontal" >

            <RelativeLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="4" >
                <RelativeLayout 
                    android:layout_width="wrap_content"
                    android:layout_height="fill_parent"
                    android:gravity="center">
                      <TextView 
                    android:id="@+id/aa"
                    android:gravity="center_vertical"
                    android:text="单位"
                    android:layout_width="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_height="fill_parent"/>
                    
                    <ScrollView 
                        android:layout_height="300dp"
                        android:layout_width="fill_parent"
                        android:layout_centerHorizontal="true"
                        android:scrollbars="none"
                        android:overScrollMode="never"
                        android:layout_toLeftOf="@id/aa"
                        >
                     	<LinearLayout 
					    android:orientation="vertical"
					    android:layout_gravity="center_horizontal"
					    android:layout_height="300dp"
					    android:layout_width="wrap_content"
					    >  <!-- 
					    	 <TextView 
                     	        android:layout_height="100dp"
                     	        android:layout_width="wrap_content"
                     	        android:textAlignment="center"
                     	        android:text=""/>
                     	    <TextView 
                     	        android:layout_height="100dp"
                     	        android:layout_width="wrap_content"
                     	        android:textAlignment="center"
                     	        android:gravity="center_vertical"                     	                             	        
                     	        android:text="1999"/>

                     	      <TextView
                     	          android:layout_width="wrap_content"
                     	          android:layout_height="100dp"
                     	          android:text="2000"
                     	          android:gravity="center_vertical"       
                     	          android:textAlignment="center" />

                     	        <TextView 
                     	        android:layout_height="100dp"
                     	        android:gravity="center_vertical"       
                     	        android:layout_width="wrap_content"
                     	        android:text="2001"/>
                     	          <TextView 
                     	        android:layout_height="100dp"
                     	        android:gravity="center_vertical"       
                     	        android:layout_width="wrap_content"
                     	        android:text="2002"/>
                     	            <TextView 
                     	        android:layout_height="100dp"
                     	        android:gravity="center_vertical"       
                     	        android:layout_width="wrap_content"
                     	        android:text="2003"/>
                     	              <TextView 
                     	        android:layout_height="100dp"
                     	        android:gravity="center_vertical"       
                     	        android:layout_width="wrap_content"
                     	        android:text="2004"/>
                     	                <TextView 
                     	        android:layout_height="100dp"
                     	        android:gravity="center_vertical"       
                     	        android:layout_width="wrap_content"
                     	        android:text="2005"/>
                     	                  <TextView 
                     	        android:layout_height="100dp"
                     	        android:gravity="center_vertical"       
                     	        android:layout_width="wrap_content"
                     	        android:text="2006"/>
                     	                  <TextView 
                     	        android:layout_height="100dp"
                     	        android:gravity="center_vertical"       
                     	        android:layout_width="wrap_content"
                     	        android:textAlignment="center"
                     	        android:text=""/> -->
                     	</LinearLayout>               
                    </ScrollView>
					      
                </RelativeLayout>
              
            </RelativeLayout>

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:background="@drawable/btton_avtie"
                android:text="确定"
                android:layout_weight="3"
                android:textSize="30dp" />
        </LinearLayout>
    </RelativeLayout>

   
</RelativeLayout>

附上效果图:

ssaaa

相信大家看布局文件还是看得懂的,第二个relativeLayout就是控件,我们的任务就是把这些xml写成代码(有些个别设置与xml的不同,注意属性的差别)

我决定分三个类,第一个是WheelView,来表示这个控件,也就是说它便是第二个relativeLayout

第二个类是CheckNumView,它表示第三个relativeLayout

第三个类是WheelScrollView,它表示ScrollView

显然,这个三个类的关系很清楚,就是后一个嵌套在前一个里面

至于其他控件,例如确定按钮,大家看布局文件就应该可以加上

 

下面我从MainActivity开始说起,为了表示轮子,我建立了一个JAVABEAN,也就是Wheel类,这个类存储每个轮子里面的数据。

package com.androidtest;

public class Wheel {
	/**
	 * 内容
	 */
	private String[] texts;
	
	/**
	 * 焦点文字
	 */
	private String focusText;
	
	public Wheel(String[] texts){
		this.texts = texts;
	}
	
	public String[] getTexts() {
		return texts;
	}
			
	public int getFocusTextPosition() {
		int position = 0;
		int count = texts.length;
		if(count > 0 ){
			for (int i = 0; i < texts.length; i++) {
				if(texts[i].equals(focusText)) {
					position = i;
				}
			}
			if(position == 0) {
				position = -1;
			}
		}else{
			position = -1;
		}
		
		return position;
	}
}

然后是Activity

package com.androidtest;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;

public class MainActivity extends ActionBarActivity {
	WheelView wheelView; 
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		wheelView = (WheelView) findViewById(R.id.wheelview);
		String[] years = {"1999","2000","2001","2002","2003","2004","2005","2006","2007","2008","2009","2010","2011","2012"};
		String[] mons = {"1999","2000","2001","2002","2003","2004","2005","2006","2007","2008","2009","2010","2011","2012"};
		Wheel w1 = new Wheel(years);
		Wheel w2 = new Wheel(mons);
		Wheel[] ws = {w1,w2};
		wheelView.setWheels(ws);
		
	}	
}

从上面的代码看出,我的初衷就是,每创建一个轮子Wheel,将它加入数组,就可以动态增加轮子

再看activity_main.xml

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.androidtest.MainActivity" >

    <com.androidtest.WheelView
        android:id="@+id/wheelview"
        android:layout_width="wrap_content"
        android:background="@drawable/act_menu_bg_hover"
        android:layout_height="wrap_content"/>

</RelativeLayout>

注意,我们刚才的test.xml只是为了我思考方便的,实际上并不需要用到,真正在布局的,是在爱activity_main.xml里面增加自定义控件

然后是WheelView

package com.androidtest;

import java.util.Arrays;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.RelativeLayout.LayoutParams;

public class WheelView extends RelativeLayout {
	static int rowHeight  = 100;
	Context context;
	private CheckNumView[] numberViews;
	public WheelView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.context = context;	
	}

	/**
	 * 获得结果
	 * @return
	 */
	public String[] getResult(){
		String[] nums = new String[numberViews.length];
		for (int i = 0; i < numberViews.length; i++) {
			nums[i] = numberViews[i].getNumber();
		}
		return nums;
	}
	
	@SuppressLint("NewApi")
	public void setWheels(Wheel[] wheels) {
		//轮子数组
		numberViews = new CheckNumView[wheels.length];
		
		//中间蓝色的遮蔽层
		ImageView imageView = new ImageView(context);
		imageView.setBackgroundResource(R.drawable.shoukuan_border4);
		RelativeLayout.LayoutParams lp1 =  new RelativeLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		lp1.height = rowHeight;
		lp1.setMargins(0, rowHeight, 0, 0);
		imageView.setLayoutParams(lp1);		
		addView(imageView);
		
		//下面就是包裹滚轮的LinearLayout
		LinearLayout llayout = new LinearLayout(context);
		LinearLayout.LayoutParams lp2 =  new LinearLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		lp2.height = rowHeight*3;
		llayout.setOrientation(LinearLayout.HORIZONTAL);
		llayout.setLayoutParams(lp2);
		
		//将滚轮添加到LinearLayout里面
		int i = 0;
		for(Wheel wheel : wheels){
			RelativeLayout rlayout = new RelativeLayout(context);
			LinearLayout.LayoutParams lp3 =  new LinearLayout.LayoutParams(
					LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
			lp3.width = 0;
			lp3.weight = 4;						
			numberViews[i] = new CheckNumView(context,wheel);				
			llayout.addView(numberViews[i],lp3);
			i++;
		}
		
		//右边的确定按钮
		Button btn = new Button(context);
		btn.setText("确定");
		btn.setTextSize(30);
		btn.setBackgroundResource(R.drawable.btton_avtie);
		LinearLayout.LayoutParams lp4 =  new LinearLayout.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
		lp4.gravity = Gravity.CENTER;
		lp4.weight = 3;
		//点击按钮,弹出选中数据
		btn.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				Toast.makeText(context, Arrays.toString(getResult()), Toast.LENGTH_SHORT).show();;
			}
		});
		
		llayout.addView(btn,lp4);
		
		addView(llayout);
	}
		
}

然后是CheckNumView,其实每个CheckNumView就是单独一个滚轮,然而它仍然是继承RelativeLayout,而不是ScroolView,是为了更方便的调整滚轮的位置,况且,滚轮旁边还有一个标志单位的TextView,显然它个滚轮(ScroolView)应该是一个整体,所以我们把ScroolView和单位TextView先包装成一个整体

package com.androidtest;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.view.Gravity;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.androidtest.WheelScrollView.OnScrollStopListener;

public class CheckNumView extends RelativeLayout{
	WheelScrollView sc;
	String[] texts;
	private int currentY = -1000;
	private int position = 1;
	public CheckNumView(Context context) {
		super(context);
	}
	
	@SuppressLint("NewApi")
	public CheckNumView(Context context, Wheel wheel) {
		super(context);
		//获取数据字符串数组
		texts = wheel.getTexts();
		
		//这个RelativeLayout用于包裹滚轮
		RelativeLayout rlayout = new RelativeLayout(context);
		RelativeLayout.LayoutParams lp =  new RelativeLayout.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);		
		rlayout.setGravity(Gravity.CENTER);
		
		//单位TextView
		TextView unit = new TextView(context);
		unit.setText("单位");
		unit.setId(1111);
		unit.setGravity(Gravity.CENTER);
		RelativeLayout.LayoutParams lp2 =  new RelativeLayout.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
		lp2.addRule(RelativeLayout.CENTER_VERTICAL,RelativeLayout.TRUE );
		lp2.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
		rlayout.addView(unit,lp2);
		
		//滚轮
		sc = new WheelScrollView(context,texts);
		sc.setVerticalScrollBarEnabled(false);
		sc.setHorizontalScrollBarEnabled(false);
		sc.setOverScrollMode(OVER_SCROLL_NEVER);
		RelativeLayout.LayoutParams lp3=  new RelativeLayout.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
		lp3.height = WheelView.rowHeight * 3;
		lp3.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE );
		lp3.addRule(RelativeLayout.LEFT_OF, 1111);				
		
		//这个方法,是指滚轮初始化以后的第一个位置
		sc.post(new Runnable() {
			
			@Override
			public void run() {				
				sc.scrollTo(0, 0*WheelView.rowHeight);
			}
		});
		//这个方法,设置选中位置字符串的颜色
		setFocusText(1);
		
		/*
		 * 这个是回调监听器,一旦滚轮停止滚动,就是触发
		 * 有必要说下的是,currentY必须不断更新
		 */
		sc.setOnScrollStopListener(new OnScrollStopListener(){

			@Override
			public void onStop(int y) {
				if(y != currentY) {
					// 判断滚动误差,不到行高的一半就抹掉,超过行高的一半而不到一个行高就填满
					if (y % WheelView.rowHeight >= (WheelView.rowHeight / 2)) {
						y = y + WheelView.rowHeight - y % WheelView.rowHeight;
						sc.scrollTo(0, y);
					} else {
						y = y - y % WheelView.rowHeight;
						sc.scrollTo(0, y);
					}
					setFocusText(y / WheelView.rowHeight+1);
				}
				currentY = y;
			}
			
		});
		
		rlayout.addView(sc,lp3);
		
		addView(rlayout,lp);																
	}		
	
	/**
	 * 设置焦点文字风格
	 * 
	 * @param position
	 */
	private void setFocusText(int position) {
		if(this.position >= 0) {
			sc.textViews[this.position].setTextColor(Color.BLACK);
		}
		sc.textViews[position].setTextColor(Color.RED);
		this.position = position;
	}

	public String getNumber() {
		return texts[position-1];
	}
}

看到这里,如果有人被弄糊涂了,那么请记住我上面给出的第一个任务,实现布局。

至于这里的setOnScrollStopListener方法,我们可以暂时不管它,因为它与布局的师兄无关

最后再看WheelScrollView

package com.androidtest;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.view.Gravity;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;

public class WheelScrollView extends ScrollView implements Runnable{
	private String[] texts;
	public boolean isStop = false;
	private Thread t;
	private int y;
	private int curY = 0;
	public TextView[] textViews;
	/*
	 * 使用handler是为了修改主线程ui,也就是CheckNumView里面的setFocusText()方法
	 * 如果不需要改变ui,我大可不必使用handler,直接用一个子线程来通知listener就可以了
	 */
	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			if (isStop) {
				listener.onStop(curY);
				isStop = false;
			}
			y = -100;
			curY = 0;
		}

	};
	
	//监听器
	private OnScrollStopListener listener;
	
	public WheelScrollView(Context context,String[] texts) {
		super(context);
		this.texts = texts;
		//scrollview里面的textViews
		textViews = new TextView[texts.length+2];
		
		//scrollview里面LinearLayout
		LinearLayout llayout = new LinearLayout(context);
		RelativeLayout.LayoutParams lp4=  new RelativeLayout.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
		lp4.height =  WheelView.rowHeight * 3;
		lp4.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE );
		llayout.setOrientation(LinearLayout.VERTICAL);		
		
		/*
		 * 下面将textViews逐一加到LinearLayout里面
		 * 并且设置头一个空白的textViews,跟尾一个空白的textViews,这样的目的是因为我们选中的项是在中间
		 */
		RelativeLayout.LayoutParams lp5=  new RelativeLayout.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
		lp5.height = WheelView.rowHeight;
		textViews[0] = new TextView(context);
		textViews[0].setGravity(Gravity.CENTER_VERTICAL);			
		textViews[0].setText("");		
		llayout.addView(textViews[0],lp5);
		int i = 1;
		for(String text :texts){
			textViews[i] = new TextView(context);
			textViews[i].setGravity(Gravity.CENTER_VERTICAL);			
			textViews[i].setText(text);
			llayout.addView(textViews[i],lp5);
			i++;
		}
		textViews[i] = new TextView(context);
		textViews[i].setGravity(Gravity.CENTER_VERTICAL);			
		textViews[i].setText("");
		llayout.addView(textViews[i],lp5);
		
		//将LinearLayout加入ScrollView
		addView(llayout,lp4);
	}
	
	//滚动时自动调用该函数,获取y值
	@Override
	protected void onScrollChanged(int x, int y, int oldx, int oldy) {
		super.onScrollChanged(x, y, oldx, oldy);
		this.y = y < 0 ? 0 : y;
	}
	
	//回调接口
	public static interface OnScrollStopListener {
		public void onStop(int y);
	}
	public void setOnScrollStopListener(OnScrollStopListener listener) {
		this.listener = listener;
	}

	//减少滚动的速度
	@Override
	public void fling(int velocityY) {
		super.fling(velocityY / 3);
	}
		
	
	//这里是判断滚动触发开始,与滚动触发停止的
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if(ev.getAction()==MotionEvent.ACTION_UP){
			isStop = true;
			if (t == null) {
				t = new Thread(this);
				t.start();
			} else if (!t.isAlive()) {
				t = new Thread(this);
				t.start();
			}
		}else if(ev.getAction()==MotionEvent.ACTION_DOWN){
			isStop = false;
		}
		return super.onTouchEvent(ev);
	}
	
	//如果通知滚动,新线程将使用handler请求修改ui,并且调用回调函数,式选项在正确的位置上
	@Override
	public void run() {
		while (isStop) {		
			try {
				if (curY == y) {
					handler.sendEmptyMessage(0);
				} else {
					curY = y;
				}
				Thread.sleep(60);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

同样,如果这都代码看不懂,你可以先忽略一些与布局无关的东西(除了构造函数,基本其他函数都与布局无关)。

忽略这些代码以后,我相信已经可以画出这个控件,并且可以拖动了

下面的问题就是我们希望拖到两个选项中间,脱手时,会自动对准某一个最近的选项

这是我们就需要用到其他的代码了。

思路是使用onTouchEvent(MotionEvent ev)来判断滑动开始与结束

一点滑动结束,我们就要拿到当前的y值,然后通过一个线程,调用handler去通知CheckNumView里面的OnScrollStopListener,最后我们在onstop()函数里面,处理这个y值

一个疑问是为什么获得y值以后,要通过线程调用handler,理由是防止再次TouchEvent影响前一次TouchEvent的结果

第二个疑问是,为什么要记录curY,因为只有curY==y,我们才能确定滑动停止了

OK,几个因为解决了,相信大家看着我的代码,应该豁然开朗了

 

源码下载

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
好的,以下是一个简单的示例代码,展示如何使用滚轮选择器展示性别选择弹窗: 首先,我们需要在我们的布局文件中添加一个按钮和一个TextView,用于展示选择结果: ```xml <Button android:id="@+id/btn_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="选择性别"/> <TextView android:id="@+id/tv_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="未选择"/> ``` 然后,我们需要创建一个弹窗,用于展示滚轮选择器。我们可以使用AlertDialog来创建弹窗: ```java final String[] genders = {"男", "女"}; // 性别选项数组 final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("请选择性别"); builder.setSingleChoiceItems(genders, -1, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 点击选项时的回调函数 String gender = genders[which]; tvGender.setText(gender); dialog.dismiss(); // 关闭弹窗 } }); builder.setNegativeButton("取消", null); AlertDialog dialog = builder.create(); ``` 在这个示例中,我们使用了setSingleChoiceItems方法来创建一个单选列表对话框,并传入了性别选项数组和一个回调函数。回调函数中会根据用户选择的选项更新TextView的文本,并关闭弹窗。 最后,我们需要在按钮的点击事件中展示弹窗: ```java btnGender.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.show(); // 展示弹窗 } }); ``` 现在,当用户点击选择性别按钮时,弹窗会展示一个滚轮选择器,让用户选择性别,并在用户选择后更新TextView的文本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值