2015-09-11 10:22:26 hehaiminginadth 阅读数 6101

目前项目中需要用到自定义的数字软键盘,通过查找资料,最后写出的一个数字键盘,大家一起学习共享!

下面我们看下效果:


接下来我们看一看代码,

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"
    tools:context=".MainActivity" >

    <EditText
        android:id="@+id/editText1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginRight="24dp"
        android:ems="10"
        android:inputType="none" >

        <requestFocus />
    </EditText>

    <android.inputmethodservice.KeyboardView
        android:id="@+id/keyboardview"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginTop="5.0dp"
        android:background="#ffffffff"
        android:focusable="true"
        android:keyBackground="@drawable/calculator_button_bg"
        android:keyTextColor="@color/black"
        android:keyTextSize="26.0sp"
        android:shadowColor="#ffffffff"
        android:shadowRadius="0.0"
        android:visibility="gone" />

</RelativeLayout>

CustomKeyboard.java:

package com.example.ex_exp;

import android.app.Activity;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
import android.text.Editable;
import android.text.InputType;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;

/**
 * When an activity hosts a keyboardView, this class allows several EditText's
 * to register for it.
 * 
 */
class CustomKeyboard
{

	/** A link to the KeyboardView that is used to render this CustomKeyboard. */
	private KeyboardView mKeyboardView;
	/** A link to the activity that hosts the {@link #mKeyboardView}. */
	private Activity mHostActivity;

	/** The key (code) handler. */
	private OnKeyboardActionListener mOnKeyboardActionListener = new OnKeyboardActionListener()
	{

		public final static int CodeDelete = -5; // Keyboard.KEYCODE_DELETE
		public final static int CodeCancel = -3; // Keyboard.KEYCODE_CANCEL

		@Override
		public void onKey(int primaryCode, int[] keyCodes)
		{
			// NOTE We can say '<Key android:codes="49,50" ... >' in the xml
			// file; all codes come in keyCodes, the first in this list in
			// primaryCode
			// Get the EditText and its Editable
			View focusCurrent = mHostActivity.getWindow().getCurrentFocus();
			if (focusCurrent == null
					|| focusCurrent.getClass() != EditText.class)
				return;
			EditText edittext = (EditText) focusCurrent;
			Editable editable = edittext.getText();
			int start = edittext.getSelectionStart();
			// Apply the key to the edittext
			if (primaryCode == CodeCancel)
			{
				hideCustomKeyboard();
			}
			else if (primaryCode == CodeDelete)
			{
				if (editable != null && start > 0)
					editable.delete(start - 1, start);
			}
			else
			{ // insert character
				editable.insert(start, Character.toString((char) primaryCode));
			}
		}

		@Override
		public void onPress(int arg0)
		{
		}

		@Override
		public void onRelease(int primaryCode)
		{
		}

		@Override
		public void onText(CharSequence text)
		{
		}

		@Override
		public void swipeDown()
		{
		}

		@Override
		public void swipeLeft()
		{
		}

		@Override
		public void swipeRight()
		{
		}

		@Override
		public void swipeUp()
		{
		}
	};

	/**
	 * Create a custom keyboard, that uses the KeyboardView (with resource id
	 * <var>viewid</var>) of the <var>host</var> activity, and load the keyboard
	 * layout from xml file <var>layoutid</var> (see {@link Keyboard} for
	 * description). Note that the <var>host</var> activity must have a
	 * <var>KeyboardView</var> in its layout (typically aligned with the bottom
	 * of the activity). Note that the keyboard layout xml file may include key
	 * codes for navigation; see the constants in this class for their values.
	 * Note that to enable EditText's to use this custom keyboard, call the
	 * {@link #registerEditText(int)}.
	 * 
	 * @param host
	 *            The hosting activity.
	 * @param viewid
	 *            The id of the KeyboardView.
	 * @param layoutid
	 *            The id of the xml file containing the keyboard layout.
	 */
	public CustomKeyboard(Activity host, int viewid, int layoutid)
	{
		mHostActivity = host;
		mKeyboardView = (KeyboardView) mHostActivity.findViewById(viewid);
		mKeyboardView.setKeyboard(new Keyboard(mHostActivity, layoutid));
		mKeyboardView.setPreviewEnabled(false); // NOTE Do not show the preview
		// balloons
		mKeyboardView.setOnKeyboardActionListener(mOnKeyboardActionListener);
		// Hide the standard keyboard initially
		mHostActivity.getWindow().setSoftInputMode(
				WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
	}

	/** Returns whether the CustomKeyboard is visible. */
	public boolean isCustomKeyboardVisible()
	{
		return mKeyboardView.getVisibility() == View.VISIBLE;
	}

	/**
	 * Make the CustomKeyboard visible, and hide the system keyboard for view v.
	 */
	public void showCustomKeyboard(View v)
	{
		mKeyboardView.setVisibility(View.VISIBLE);
		mKeyboardView.setEnabled(true);
		if (v != null)
			((InputMethodManager) mHostActivity
					.getSystemService(Activity.INPUT_METHOD_SERVICE))
					.hideSoftInputFromWindow(v.getWindowToken(), 0);
	}

	/** Make the CustomKeyboard invisible. */
	public void hideCustomKeyboard()
	{
		mKeyboardView.setVisibility(View.GONE);
		mKeyboardView.setEnabled(false);
	}

	/**
	 * Register <var>EditText<var> with resource id <var>resid</var> (on the
	 * hosting activity) for using this custom keyboard.
	 * 
	 * @param resid
	 *            The resource id of the EditText that registers to the custom
	 *            keyboard.
	 */
	public void registerEditText(int resid)
	{
		// Find the EditText 'resid'
		EditText edittext = (EditText) mHostActivity.findViewById(resid);
		// Make the custom keyboard appear
		edittext.setOnFocusChangeListener(new OnFocusChangeListener()
		{
			@Override
			public void onFocusChange(View v, boolean hasFocus)
			{
				if (hasFocus)
					showCustomKeyboard(v);
				else
					hideCustomKeyboard();
			}
		});
		
		edittext.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				showCustomKeyboard(v);
			}
		});
		
		edittext.setOnTouchListener(new OnTouchListener()
		{
			@Override
			public boolean onTouch(View v, MotionEvent event)
			{
				EditText edittext = (EditText) v;
				int inType = edittext.getInputType(); // Backup the input type
				edittext.setInputType(InputType.TYPE_NULL); // Disable standard
				// keyboard
				edittext.onTouchEvent(event); // Call native handler
				edittext.setInputType(inType); // Restore input type
				return true; // Consume touch event
			}
		});
		// Disable spell check (hex strings look like words to Android)
		edittext.setInputType(edittext.getInputType()
				| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
	}

}

hexkbd.xml:

<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="#ffffffff"
    android:keyHeight="10%p"
    android:keyWidth="100%p" >

    <Row>
        <Key
            android:codes="55"
            android:keyEdgeFlags="left"
            android:keyLabel="7" />
        <Key
            android:codes="56"
            android:keyLabel="8" />
        <Key
            android:codes="57"
            android:keyLabel="9" />
    </Row>
    <Row>
        <Key
            android:codes="52"
            android:keyEdgeFlags="left"
            android:keyLabel="4" />
        <Key
            android:codes="53"
            android:keyLabel="5" />
        <Key
            android:codes="54"
            android:keyLabel="6" />
    </Row>
    <Row>
        <Key
            android:codes="49"
            android:keyEdgeFlags="left"
            android:keyLabel="1" />
        <Key
            android:codes="50"
            android:keyLabel="2" />
        <Key
            android:codes="51"
            android:keyLabel="3" />
    </Row>
    <Row>
        <Key
            android:codes="46"
            android:keyEdgeFlags="left"
            android:keyLabel="." />
        <Key
            android:codes="48"
            android:keyLabel="0" />
        <Key
            android:codes="-5"
            android:isRepeatable="true"
            android:keyIcon="@drawable/sym_keyboard_delete" />
    </Row>

</Keyboard>
最后看下MainActivity.java:

package com.example.ex_exp;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;

public class MainActivity extends Activity implements OnClickListener
{
	CustomKeyboard mCustomKeyboard;
	EditText editText;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		editText = (EditText) findViewById(R.id.editText1);
		mCustomKeyboard = new CustomKeyboard(this, R.id.keyboardview,
				R.xml.hexkbd);

		mCustomKeyboard.registerEditText(R.id.editText1);
	}

	@Override
	public void onClick(View v)
	{
		switch (v.getId())
		{
		default:
			break;
		}
	}
}


以上就是功能的实现了!


下载地址如下:

http://download.csdn.net/detail/hehaiminginadth/9097191

2017-12-01 11:00:50 qq_20328181 阅读数 2166

android 仿ios数字密码解锁界面

 

每个Android开发人员都知道,现在android的解锁最常用的就是九宫格解锁,ios的解锁常用的是数字密码解锁。而我们在开发工程中,很多时候,都需要android和ios进行结合。有的时候我们就需要把我们的解锁界面弄成像ios一样的数字键盘。
这里我就实现了一个仿照ios的数字密码解锁界面。在这里我采用了两种方式来实现,第一种就是使用自定义控件的形式,第二种就是使用我们的布局来实现的。这里我就着重讲一下使用自定义控件形式实现的思路。至于使用布局文件实现的方式,我就不进行具体的讲解了,具体的在后面我会上传源码供大家下载进行研究学习。
当然,我的能力有限,可能你们觉得我的方式不好,那么你们就给我提出来,看看具体你们觉得怎么实现才好,我们一起研究学习。
好了,废话不多说了,现在就来讲解一下,我所使用自定义控件的实现方法:
1.最主要还是实现自定义数字键盘(一个自定义View的控件):
1.绘制数字
// 绘制第一排1,2,3
  canvas.drawText("1", first_x, 40 + first_y, paint);
  canvas.drawText("2", first_x * 2, 40 + first_y, paint);
  canvas.drawText("3", first_x * 3, 40 + first_y, paint);
  // 绘制第2排4,5,6
  canvas.drawText("4", first_x, 40 + first_y + first_x, paint);
  canvas.drawText("5", first_x * 2, 40 + first_y + first_x, paint);
  canvas.drawText("6", first_x * 3, 40 + first_y + first_x, paint);
  // 绘制第3排7,8,9
  canvas.drawText("7", first_x, 40 + first_y + first_x * 2, paint);
  canvas.drawText("8", first_x * 2, 40 + first_y + first_x * 2, paint);
  canvas.drawText("9", first_x * 3, 40 + first_y + first_x * 2, paint);
  // 绘制第4排0
  canvas.drawText("0", first_x * 2, 40 + first_y + first_x * 3, paint);

2.绘制好数字后,我们需要在每一个数字外面添加一层圆,绘制圆
//依次绘制第一排的圆
  canvas.drawCircle(first_x+10, 40 + first_y - 15, 50, paint);
  canvas.drawCircle(first_x*2+10, 40 + first_y - 15, 50, paint);
  canvas.drawCircle(first_x*3+10, 40 + first_y - 15, 50, paint);
  //依次绘制第2排的圆
  canvas.drawCircle(first_x+10, 40 + first_y + first_x - 15, 50, paint);
  canvas.drawCircle(first_x*2+10, 40 + first_y + first_x - 15, 50, paint);
  canvas.drawCircle(first_x*3+10, 40 + first_y + first_x - 15, 50, paint);
  //依次绘制第3排的圆
  canvas.drawCircle(first_x+10, 40 + first_y + first_x * 2 - 15, 50, paint);
  canvas.drawCircle(first_x*2+10, 40 + first_y + first_x * 2 - 15, 50, paint);
  canvas.drawCircle(first_x*3+10, 40 + first_y + first_x * 2 - 15, 50, paint);
  //绘制最后一个圆
  canvas.drawCircle(first_x*2+10, 40 + first_y + first_x * 3 - 15, 50, paint);

3.点击数字后,我们给一个不同的效果,让用户知道自己点击了该数字,这里我是吧外面的圆的颜色改为了黄色
//判断是否点击数字
  if(circle_x > 0 && circle_y > 0){//点击
   if(type == 0){//按下刷新
    paint.setColor(Color.YELLOW);//设置画笔颜色
    canvas.drawCircle(circle_x, circle_y, 50, paint);//绘制圆
   }else if(type == 1){//弹起刷新
    paint.setColor(Color.WHITE);//设置画笔颜色
    canvas.drawCircle(circle_x, circle_y, 50, paint);//绘制圆
    //绘制完成后,重置
    circle_x = 0; circle_y = 0;
   }
  }

4.最后就是判断点击的数字
/*
  * 判断点击的是哪一个数字圆
  */
private void handleDown(float x, float y){
  //判断点击的是那一列的数据
  if(xs[0] - 50 <= x && x <= xs[0] + 50){//第一列
   //获取点击处的圆心横坐标
   circle_x = xs[0];
   //判断点击的是哪一排
   if(ys[0] - 50 <= y && ys[0] + 50 >= y){//第1排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[0];
    number = 1;//设置点击的数字
   }else if(ys[1] - 50 <= y && ys[1] + 50 >= y){//第2排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[1];
    number = 4;//设置点击的数字
   }else if(ys[2] - 50 <= y && ys[2] + 50 >= y){//第3排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[2];
    number = 7;//设置点击的数字
   }
  }else if(xs[1] - 50 <= x && x <= xs[1] + 50){//第2列
   //获取点击处的圆心横坐标
   circle_x = xs[1];
   //判断点击的是哪一排
   if(ys[0] - 50 <= y && ys[0] + 50 >= y){//第1排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[0];
    number = 2;//设置点击的数字
   }else if(ys[1] - 50 <= y && ys[1] + 50 >= y){//第2排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[1];
    number = 5;//设置点击的数字
   }else if(ys[2] - 50 <= y && ys[2] + 50 >= y){//第3排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[2];
    number = 8;//设置点击的数字
   }else if(ys[3] - 50 <= y && ys[3] + 50 >= y){//第4排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[3];
    number = 0;//设置点击的数字
   }
  }else if(xs[2] - 50 <= x && x <= xs[2] + 50){//第3列
   //获取点击处的圆心横坐标
   circle_x = xs[2];
   //判断点击的是哪一排
   if(ys[0] - 50 <= y && ys[0] + 50 >= y){//第1排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[0];
    number = 3;//设置点击的数字
   }else if(ys[1] - 50 <= y && ys[1] + 50 >= y){//第2排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[1];
    number = 6;//设置点击的数字
   }else if(ys[2] - 50 <= y && ys[2] + 50 >= y){//第3排
    //获取点击的数字圆的圆心纵坐标
    circle_y = ys[2];
    number = 9;//设置点击的数字
   }
  }
  sendAccessEvent(R.string.numeric_keyboard_down);
  type = 0;//按下刷新
  //绘制点击时的背景圆
  invalidate();
}

好了,大概我的就是这样了。顺带提一下,我这里上面4个显示密码的控件也是采用的自定义控件的方式,使用线程实现输入数字后1秒后用密码字符替换输入的数字。(有人可能会说系统的EditText控件设置样式为密码也可以实现,这里我想说的是,不可以的,至少我试了是不行的)

2017-03-30 10:41:25 y_weiqiang 阅读数 5701

转载:http://blog.csdn.NET/bfbx5173/article/details/45191147

自学Android差不多有一年了,从最初的小白菜鸟,摸爬滚打,看大神们的博客,android官网的api,某网站的视频教学,github开源项目。奋斗这么久隐隐感觉自己可以脱离新手的身份了,交出这篇文章权当作andriod小学水准的毕业典礼。

iOS SwitchButton。  说实话功能也不过就个开关功能而已。但是为什么让人感觉不错,因为效果看起来赏心悦目呀:

~~~~~~~~~~~~~~~~~


好了,为了实现它,首先要分析它。

这个按钮被我玩来玩去最后静止的时候都会停留在下面的样子:

 

那么怎么把这个实现出来呢?观察一番会发现上图是极其规律的,只是一些基础几何图形的组合。所以具备纯代码实现可能性,同时如果用图片实现这个效果需要对应的png文件辅助,相信大家一定觉得麻烦。

那么就把它画出来!如何画出来的分析路线:

1. 位置固定不变的背景,像田径场一样的形状。

2. 圆圆的按钮,压在“田径场”上面。  【之后背景全称作"田径场",比较形象,不服solo 囧】

3. 淡淡的按钮阴影,夹在他们之间。

ps:哎,我还是分析的那么透彻,赞一个。


开始动手!新建

[html] view plain copy
  1. public class SwitchView extends View {  
  2.     public SwitchView(Context context) {  
  3.         this(context, null);  
  4.     }  
  5.   
  6.     public SwitchView(Context context, AttributeSet attrs) {  
  7.         super(context, attrs);  
  8.         setLayerType(LAYER_TYPE_SOFTWARE, null);  
  9.     }  
  10. }  

之后为自己确定大小~  截图量了一下 算上阴影宽高比例是 149:92 。即 height = width * 0.65 左右

[java] view plain copy
  1. @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  3.     int heightSize = (int) (widthSize * 0.65f);  
  4.     setMeasuredDimension(widthSize, heightSize);  
  5. }  
绘制~

[java] view plain copy
  1. @Override protected void onDraw(Canvas canvas) {  
  2.     super.onDraw(canvas);  
  3.     canvas.drawColor(0xffcccccc);  
  4. }  
好哒,通向胜利的第一步已经完成了。


ps: 如果我说,剩下的大家自己思考,你们不会打我吧。

第二步,画田径场!

[java] view plain copy
  1. private final Paint paint = new Paint();  
  2. private final Path sPath = new Path();  
  3.   
  4. private int mWidth, mHeight;  
  5. private float sWidth, sHeight;  
  6. private float sLeft, sTop, sRight, sBottom;  
  7. private float sCenterX, sCenterY;  
  8.   
  9. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  10.     super.onSizeChanged(w, h, oldw, oldh);  
  11.     mWidth = w; // 视图自身宽度  
  12.     mHeight = h; // 视图自身高度  
  13.     sLeft = sTop = 0// 田径场 左和上的坐标  
  14.     sRight = mWidth; // 田径场 右占自身的全部  
  15.     sBottom = mHeight * 0.8f; // 田径场底部 占全身的百分之八十, 下面预留百分之二十的空间画按钮阴影。  
  16.     sWidth = sRight - sLeft; // 田径场的宽度  
  17.     sHeight = sBottom - sTop; // 田径场的高度  
  18.     sCenterX = (sRight + sLeft) / 2// 田径场的X轴中心坐标  
  19.     sCenterY = (sBottom + sTop) / 2// 田径场的Y轴中心坐标  
  20.   
  21.     RectF sRectF = new RectF(sLeft, sTop, sBottom, sBottom);  
  22.     sPath.arcTo(sRectF, 90180);  
  23.     sRectF.left = sRight - sBottom;  
  24.     sRectF.right = sRight;  
  25.     sPath.arcTo(sRectF, 270180);  
  26.     sPath.close();    // path准备田径场的路径  
  27. }  
  28.   
  29. @Override protected void onDraw(Canvas canvas) {  
  30.     super.onDraw(canvas);  
  31.     paint.setAntiAlias(true);  
  32.     paint.setStyle(Style.FILL);  
  33.     paint.setColor(0xffcccccc);  
  34.     canvas.drawPath(sPath, paint); // 画出田径场  
  35.   
  36.     paint.reset();  
  37. }  

由于田径场不是基础规则的几何图形,只好交给万能的path对象啦。 path 左右加两段圆弧中间一连接,感谢api朋友 [arcTo]

值得注意的是预留出按钮阴影的位置。

既然都做到这个程度了,那就把背景的效果做完嘛。有什么效果呢。

额,这么搓逼,不过确实是这个效果,也许整体完成后就是另一番风景吧。orz

这个效果用大腿想象就知道是点击触发的,既然如此:

[java] view plain copy
  1. @Override public boolean onTouchEvent(MotionEvent event) {  
  2.     switch (event.getAction()) {  
  3.     case MotionEvent.ACTION_DOWN:  
  4.         return true;  
  5.     case MotionEvent.ACTION_CANCEL:  
  6.     case MotionEvent.ACTION_UP:  
  7.         sAnim = 1// 动画标示  
  8.         isOn = !isOn; // 状态标示 , 开关  
  9.         invalidate();  
  10.         break;  
  11.     }  
  12.     return super.onTouchEvent(event);  
  13. }  
这个容易,invalidate嘛。抬起手指重绘嘛,那关键问题就抛给了onDraw同志。

[java] view plain copy
  1. @Override protected void onDraw(Canvas canvas) {  
  2.     super.onDraw(canvas);  
  3.     paint.setAntiAlias(true);  
  4.     paint.setStyle(Style.FILL);  
  5.     paint.setColor(0xffcccccc);  
  6.     canvas.drawPath(sPath, paint); // 画出田径场  
  7.   
  8.     sAnim = sAnim - 0.1f > 0 ? sAnim - 0.1f : 0// 动画标示 ,重绘10次  
  9.     // draw logic - 动态改变绘制参数,达到动画效果  
  10.     paint.reset();  
  11.     if (sAnim > 0) invalidate(); // 继续重绘  
  12. }  
so,配菜都准备好了,上主菜~

分析:不用分析了,一张图大家都懂的~

看不出这是两个田径场的说明你只是地球人而已。对 就是一个白色的田径场压在了灰色的上面而已,然后大小随某个东西【anim标示】的变化而变化。

[java] view plain copy
  1. @Override protected void onDraw(Canvas canvas) {  
  2.     super.onDraw(canvas);  
  3.           
  4.     // ...  
  5.       
  6.     sAnim = sAnim - 0.1f > 0 ? sAnim - 0.1f : 0// 动画标示 ,重绘10次  
  7.           
  8.     final float scale = 0.98f * (isOn ? sAnim : 1 - sAnim); //缩放大小参数随sAnim变化而变化  
  9.     canvas.save();  
  10.     canvas.scale(scale, scale, sCenterX, sCenterY);  
  11.     paint.setColor(0xffffffff);  
  12.     canvas.drawPath(sPath, paint);  
  13.     canvas.restore();  
  14.           
  15.     paint.reset();  
  16.     if (sAnim > 0) invalidate(); // 继续重绘  
  17.           
  18.     // ...  
  19. }  
没错,还是用的sPath只不过换了个颜色而已。 我们玩的是canvas, canvas的api scale 可以缩放画布。之前申明并且计算好的sCenterX,和sCenterY的作用就是确定画布缩放中心啦。 而0.98的作用便为我们免费的留下了一条边缘。如果设置1的话,白色的田径场将完全覆盖灰色田径场,界面上就是一片白了。

为了完美适配各种分辨率,这个0.98应该被一个变量替换。
对比完成的效果图会使用感觉有点别扭,别扭的原因就是缩放的中心位置。


应该在4个箭头的起点处。就是那里:田径场的宽度 减去 按钮的一个半径,在减去按钮距离右边的间隔什么的。

那么我就要把按钮的一些参数给算好咯~

[java] view plain copy
  1. private float bRadius, bStrokWidth;  
  2. private float bWidth;  
  3. private float bLeft, bTop, bRight, bBottom;  
  4. private float sScaleCenterX;  
  5. private float sScale;  
  6.   
  7. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  8.     super.onSizeChanged(w, h, oldw, oldh);  
  9.     // ...  
  10.     bLeft = bTop = 0;  
  11.     bRight = bBottom = sBottom; // 和田径场同高,同宽的节奏, 没错包裹圆形的肯定是个正方形是小孩子都知道的。  
  12.     bWidth = bRight - bLeft;  
  13.     final float halfHeightOfS = (sBottom - sTop) / 2;  
  14.     bRadius = halfHeightOfS * 0.9f; // 按钮的半径  
  15.     bStrokWidth = 2 * (halfHeightOfS - bRadius); // 按钮的边框  
  16.   
  17.     sScale = 1 - bStrokWidth / sHeight; //替换之前的0.98<  
  18.         sScaleCenterX = sWidth - halfHeightOfS;  
  19.         // ...  
  20. }  
  21.   
  22. @Override protected void onDraw(Canvas canvas) {  
  23.         // ...  
  24.         canvas.scale(scale, scale, sScaleCenterX, sCenterY);  
  25. }  

大家估计看出我的命名规则啦:  s开头的表示田径场,b开头的表示按钮。

那么效果理所当然的变成下图所示。

继续补上按钮。

[java] view plain copy
  1. canvas.save();  
  2. paint.setStyle(Style.FILL);  
  3. paint.setColor(0xffffffff);  
  4. canvas.drawCircle(bWidth / 2, bWidth / 2, bRadius, paint); // 按钮白底  
  5. paint.setStyle(Style.STROKE);  
  6. paint.setColor(0xffdddddd);  
  7. paint.setStrokeWidth(bStrokWidth);  
  8. canvas.drawCircle(bWidth / 2, bWidth / 2, bRadius, paint); // 按钮灰边  
  9. canvas.restore();  
上面通过canvas.drawCircle的方法画了俩圆,看起来就是我们滴按钮咯。

之后动起来~这里依旧玩canvas。


相对于scale缩放方法, 他还有translate平移和retate旋转方法,是不是很牛逼~ 。这里需要的是平移方法

[java] view plain copy
  1. bTranslateX = sWidth - bWidth;  
  2.           
  3. final float translate = bTranslateX * (isOn ? 1 - sAnim : sAnim); // 平移距离参数随sAnim变化而变化  
  4.           
  5. canvas.translate(translate, 0);  
很清晰的可以看出,按钮平移的距离 是 田径场的宽度 减去按钮所占区域的宽度 , 所以即时再复杂的逻辑一点点抽丝剥茧 那么- 真相只有一个。


默默注视上面这张图,总觉得心中缺点什么? - 那就是 真实

让我们继续为其添加名为真实的动作 (Material Design)



线性动作会感到机械,在结束和开始的时候生硬的速度变化意味着物体突然开始运动或停止,这是不贴合现实的。

迅速的加速和平滑的减速会感到自然和愉快。创造了一个确信的过渡。

so 对应于代码,我们加入Interpolator来修饰SwitchView的开关动画

[java] view plain copy
  1. private final AccelerateInterpolator aInterpolator = new AccelerateInterpolator(2);  
  2.   
  3. @Override protected void onDraw(Canvas canvas) {  
  4.     super.onDraw(canvas);  
  5.     ...  
  6.     sAnim = sAnim - 0.1f > 0 ? sAnim - 0.1f : 0;  
  7.     bAnim = bAnim - 0.1f > 0 ? bAnim - 0.1f : 0;  
  8.       
  9.     final float asAnim = aInterpolator.getInterpolation(sAnim);  
  10.     Log.e("SwitchView""sAnim:" +sAnim + " ,dsAnim:" + dsAnim);  
  11.     final float abAnim = aInterpolator.getInterpolation(bAnim);  
  12.   
  13.           

那么将之后绘制逻辑中使用了sAnim,bAnim 的地方改为我们的 asAnim, abAnim 就可以啦。

看看我们的日志输出


对于这张图我就看看,我不说话。。

好了搓搓手期待终极效果吧!




好了 接下来进入我们的正题内容:

对于按钮的开关不见得是我们想开就开,想关就关的。举生活中常见的例子:小明家跳闸了,然后小明去把闸往上推,然后发现推不上去自动弹回来,再推,再弹 *n。

之后小明终于受不了打电话给闪电侠,通知它过来修。 电话拨出去的时候,并不是马上被接听,同时也有可能没人接。。。编不下去了。

特么生活的现实就是比理想残酷,小明我不是故意黑你的。


为了能够让大家明确的感受到按钮与之前的不同,所以换了一个明显的颜色~


那么转换为程序语言这个按钮开关应该是4种状态:

已经关闭。 已经打开。准备关闭。准备打开。

所以我们为switchView 添加4种状态,  之前的 变量isOn可以退休了。

[java] view plain copy
  1. private final int STATE_SWITCH_ON = 4// 已经打开  
  2. private final int STATE_SWITCH_ON2 = 3// 准备关闭  
  3. private final int STATE_SWITCH_OFF2 = 2// 准备打开  
  4. private final int STATE_SWITCH_OFF = 1// 已经关闭  
  5. private int state = STATE_SWITCH_OFF;  
  6. private int lastState = state;  


细心的朋友会发现,田径场还是那个田径场,而按钮不再是那个按钮。它可以变瘪 orz

so canvas.drawCircle就不行啦,  好了我们再来看一张图。


看不出这是两个田径场的说明你仍然只是地球人而已。对 就是一个紫色的田径场压在了灰色的上面而已,然后形状随某个东西【anim标示】的变化而在圆形和田径场之间变化而已。

[java] view plain copy
  1. private final Path bPath = new Path();  
  2. private final RectF bRectF = new RectF();  
  3.   
  4. private float bOffset;  
  5. private float bOnLeftX, bOn2LeftX, bOff2LeftX, bOffLeftX;  
  6.   
  7. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  8.     super.onSizeChanged(w, h, oldw, oldh);  
  9.     // ...  
  10.     final float halfHeightOfS = (sBottom - sTop) / 2;  
  11.     bRadius = halfHeightOfS * 0.9f;  
  12.     bOffset = bRadius * 0.3f;  // 有多瘪~  半径的三分之一左右  
  13.     bStrokWidth = 2 * (halfHeightOfS - bRadius);  
  14.   
  15.     bOnLeftX = sWidth - bWidth;  // 在已经开启状态下,按钮距离自身左端的距离  
  16.     bOn2LeftX = bOnLeftX - bOffset;// 在准备关闭状态下,按钮距离自身左端的距离  
  17.     bOffLeftX = 0;// 在已经关闭状态下,按钮距离自身左端的距离  
  18.     bOff2LeftX = 0;// 在准备开启状态下,按钮距离自身左端的距离  
  19.   
  20.     bRectF.left = bLeft;        // 替代 circle性质的按钮,改为path性质的按钮,以提供“变瘪”的功能 。囧  
  21.     bRectF.right = bRight;  
  22.     bRectF.top = bTop + bStrokWidth / 2;  
  23.     bRectF.bottom = bBottom - bStrokWidth / 2;  
  24.           
  25.     // ...  
  26. }  

那么同时再次观察刚才的gif图片。会发现在按钮变瘪的时候(已经开启到准备关闭,已经关闭到准备开启),田径场是无动作的。

所以anim标示,存在2个~ 之前这个参数叫sAnim而不是 anim,估计大家也猜出来啦。这里在添加一个 bAnim。

[java] view plain copy
  1. private float sAnim, bAnim;  

重新设定动画的触发时机!!!

[java] view plain copy
  1. @Override public boolean onTouchEvent(MotionEvent event) {  
  2.     switch (event.getAction()) {  
  3.     case MotionEvent.ACTION_DOWN:  
  4.         return true;  
  5.     case MotionEvent.ACTION_CANCEL:  
  6.     case MotionEvent.ACTION_UP:  
  7.         lastState = state;  
  8.         if (state == STATE_SWITCH_OFF) {  
  9.             bAnim = 1;  
  10.             state = STATE_SWITCH_OFF2;  
  11.         } else if (state == STATE_SWITCH_OFF2) {  
  12.             bAnim = 1;  
  13.             sAnim = 1;                         // 只有在准备打开,并且结果成功的时候  
  14.             state = STATE_SWITCH_ON;  
  15.         } else if (state == STATE_SWITCH_ON) {  
  16.             bAnim = 1;  
  17.             state = STATE_SWITCH_ON2;  
  18.         } else if (state == STATE_SWITCH_ON2) {  
  19.             bAnim = 1;  
  20.             sAnim = 1;                         // 和在准备关闭,并且结果成功的时候才会  触发背景的变化。   
  21.             state = STATE_SWITCH_OFF;  
  22.         }  
  23.         invalidate();  
  24.         break;  
  25.     }  
  26.     return super.onTouchEvent(event);  
  27. }  

这个事件处理的逻辑依旧容易,invalidate嘛。抬起手指重绘嘛,那关键问题就再一次的抛给了onDraw同志。

onDraw同志需要根据我们给的anim标示来处理按钮的动画效果咯

[java] view plain copy
  1. @Override protected void onDraw(Canvas canvas) {  
  2.     super.onDraw(canvas);  
  3.     paint.setAntiAlias(true);  
  4.     paint.setStyle(Style.FILL);  
  5.     paint.setColor(0xffcccccc);  
  6.     canvas.drawPath(sPath, paint);       // 最底下的田径场  
  7.   
  8.     sAnim = sAnim - 0.1f > 0 ? sAnim - 0.1f : 0;  
  9.     bAnim = bAnim - 0.1f > 0 ? bAnim - 0.1f : 0;  
  10.   
  11.     final boolean isOn = (state == STATE_SWITCH_ON || state == STATE_SWITCH_ON2);  
  12.     final float scale = sScale * (isOn ? sAnim : 1 - sAnim);  
  13.     final float scaleOffset = (bOnLeftX + bRadius - sCenterX) * (isOn ? 1 - sAnim : sAnim);  
  14.     canvas.save();  
  15.     canvas.scale(scale, scale, sCenterX + scaleOffset, sCenterY);        // 田径场动画的缩放中心  
  16.     paint.setColor(0xffffffff);  
  17.     canvas.drawPath(sPath, paint);       // 这个之前讲过了  
  18.     canvas.restore();  
  19.   
  20.     canvas.save();  
  21.     final boolean isState2 = (state == STATE_SWITCH_ON2 || state == STATE_SWITCH_OFF2);  
  22.     final float percent = (isState2 ? 1 - bAnim : bAnim);  
  23.     calcBPath(percent);                                   // 根据anim标示计算变瘪的按钮路径  
  24.     paint.setStyle(Style.STROKE);  
  25.     paint.setStrokeWidth(bStrokWidth);  
  26.     paint.setColor(0xffff00cc);                             
  27.     canvas.translate(calcBTranslate(bAnim), 0);           // 根据anim标示计算按钮开关平移的坐标  
  28.     canvas.drawPath(bPath, paint);  
  29.     canvas.restore();  
  30.   
  31.     paint.reset();  
  32.     if (sAnim > 0 || bAnim > 0) invalidate();             // 重绘的标示由1个变为了2个。  
  33. }  

so,白眼狼都看出来啊。啊不,口误,Oh shi-t  笔误,明眼人都看的出来。核心逻辑丢在 calcBPath() 和 calcBTranslate()里面咯。 如果问我为什么要创建2个方法丢在里面,我只能告诉你,这是kami的启示~

[java] view plain copy
  1. private void calcBPath(float percent) {  
  2.     bPath.reset();  
  3.     bRectF.left = bLeft + bStrokWidth / 2;  
  4.     bRectF.right = bRight - bStrokWidth / 2;  
  5.     bPath.arcTo(bRectF, 90180);  
  6.     bRectF.left = bLeft + percent * bOffset + bStrokWidth / 2;  
  7.     bRectF.right = bRight + percent * bOffset - bStrokWidth / 2;  
  8.     bPath.arcTo(bRectF, 270180);  
  9.     bPath.close();  
  10. }  

看方法内容,原来percent 通过直接影响rect来间接影响了path啊。  left和right值似乎随percent的增大而增大。这样 两个圆弧慢慢被拉开,反之缩小。

这特么谁写的,真是太精辟了。 --- 匿名

[java] view plain copy
  1. private float calcBTranslate(float percent) {  
  2.     float result = 0;  
  3.     int wich = state - lastState;  
  4.     switch (wich) {  
  5.     case 1// off - off2  
  6.         result = bOff2LeftX - (bOff2LeftX - bOffLeftX) * percent;  
  7.         break;  
  8.     case 2// off2 - on  
  9.         result = bOnLeftX - (bOnLeftX - bOff2LeftX) * percent;  
  10.         break;  
  11.     case -1:// on - on2  
  12.         result = bOn2LeftX + (bOnLeftX - bOn2LeftX) * percent;  
  13.         break;  
  14.     case -2:// on2 - off  
  15.         result = bOffLeftX + (bOn2LeftX - bOffLeftX) * percent;  
  16.         break;  
  17.     }  
  18.   
  19.     return result - bOffLeftX;  
  20. }  

看方法内容,原来平移的结果result 是根据不同的情况进行了分类啊,真是层次分明。

这特么谁写的,66666。   ---匿名


好了,打死我也不会告诉你们,评论是我自己留下的。

做到这里几乎完成了大半,不过仍有不合理的地方。 首先从刚才的TouchEvent的代码逻辑可以看出,点击一下动一下,点击四下一个周天~

对的。 到了准备开启后,或者准备关闭后的结果不用该由控件自身决定,应该交给业务逻辑。 那么其结果是成功还是失败就不管我们的事了,只需要提供一个

执行结果动画的公开方法就OK了。 同时,calcBTranslate()的内部逻辑便需要扩充,添加对失败结果【开启不成功,关闭不成功】的处理等。

做完这些逻辑后SwitchView便拥有延时和回滚特性 like this:

  

具体使用点击文章末尾链接查看,这里便不赘述。


那么 定义并且创建接口~

[java] view plain copy
  1. public interface OnSwitchStateChangedListener {  
  2.         void onStateChanged(int state);  
  3. }  
  4.   
  5. private OnSwitchStateChangedListener listener = new OnSwitchStateChangedListener() {  
  6.     @Override public void onStateChanged(int state) {  
  7.         if (state == STATE_SWITCH_OFF2) {  
  8.             toggleSwitch(STATE_SWITCH_ON);  
  9.         }  
  10.         else if (state == STATE_SWITCH_ON2) {  
  11.             toggleSwitch(STATE_SWITCH_OFF);  
  12.         }  
  13.     }  
  14. };  
  15.   
  16. public void setOnSwitchStateChangedListener(OnSwitchStateChangedListener listener) {  
  17.         if (listener == nullthrow new IllegalArgumentException("empty listener");  
  18.         this.listener = listener;  
  19. }  

仔细的朋友又会发现。我这里已经有个接口的默认实现啦。 实现的内容:当状态变换为准备关闭的时候 ,准备打开的时候做了一件不可告人的事情。

[java] view plain copy
  1. private void refreshState(int newState) {  
  2.     lastState = state;  
  3.     state = newState;  
  4.     postInvalidate();     // 为什么postInvalidate() 而不用 invalidate()。  你猜~  
  5. }  
  6.       
  7. private synchronized void toggleSwitch(int wich) {  
  8.     if (wich == STATE_SWITCH_ON || wich == STATE_SWITCH_OFF) {  
  9.         if ((wich == STATE_SWITCH_ON && (lastState == STATE_SWITCH_OFF || lastState == STATE_SWITCH_OFF2))  
  10.                 || (wich == STATE_SWITCH_OFF && (lastState == STATE_SWITCH_ON || lastState == STATE_SWITCH_ON2))) {  
  11.             sAnim = 1;  
  12.         }  
  13.         bAnim = 1;  
  14.         refreshState(wich);  
  15.   
  16.     }  
  17.     else {  
  18.         Log.e("SwitchView_step2""do not support state : " + wich);  
  19.     }  
  20. }  


toggleSwitch()。 根据当前状态和上次状态判断 在成功打开,和成功关闭的时候 开启sAnim田径场背景动画标示~ ,同时必然开始按钮的动画标示~之后重绘。

所以当这个控件没有被重新设置SwitchStateChangedListener的时候一切就是期望的那么美好。 想开就开,想关就关~


接着提供对外方法

[java] view plain copy
  1. public int getState() {                       // 获得状态  
  2.     return state;  
  3. }  
  4.   
  5. public void setState(boolean isOn) {          // 设置状态 只能设置 [已经关闭] 和 [已经开启]  
  6.     final int wich = isOn ? STATE_SWITCH_ON : STATE_SWITCH_OFF;  
  7.     refreshState(wich);  
  8. }  
  9.   
  10. public void toggleSwitch(boolean letItOn) {   // 切换状态 只支持 [已经关闭] 和 [已经开启] 的切换  
  11.     final int wich = letItOn ? STATE_SWITCH_ON : STATE_SWITCH_OFF;  
  12.     postDelayed(new Runnable() {  
  13.         @Override public void run() {  
  14.             toggleSwitch(wich);  
  15.         }  
  16.     }, 300);  
  17. }  


    

那么300行左右的代码 完成了我们的仿iOS SwitchButton 的控件 SwitchView (就不和它一个名字,不服 solo)生气


感谢 19楼 Chen_Ace 朋友提出的一些问题。2015年11月20号我又复查更新了代码。

关于View状态的保存,先上效果图。


之前声明了一个属性state,我经过仔细思考发现这个属性的主要作用是描述UI的状态。而事实上这个状态并不是逻辑所需要的。之前提供了setState(boolean newState)和int getState() [返回值可能为STATE_SWITCH_ON, STATE_SWITCH_ON2, STATE_SWITCH_OFF2, STATE_SWITCH_OFF]事实上前欠缺考虑了。

这里我新定义一个boolean类型变量isOpened表示当前的SwitchView的逻辑状态。代码走起~

[java] view plain copy
  1. private boolean isOpened = false;  
  2.   
  3. // 刷新UI前都会经过这里,当UI状态确定为STATE_SWITCH_ON或者STATE_SWITCH_OFF的时候更改逻辑状态。  
  4. private void refreshState(int newState) {  
  5.     if(!isOpened && newState == STATE_SWITCH_ON) {  
  6.         isOpened = true;  
  7.     } else if (isOpened && newState == STATE_SWITCH_OFF) {  
  8.         isOpened = false;  
  9.     }  
  10.     lastState = state;  
  11.     state = newState;  
  12.     postInvalidate();  
  13. }  
  14.   
  15. public void toggleSwitch(final boolean isOpened) {  
  16.     // 在这里。提供给调用者的公开方法,传入参数isOpened的值就是调用者所希望立马设置的状态。所以这里直接赋值。  
  17.     // 不必要也不能等到refreshState[UI刷新的时候]的时候再赋值。  
  18.     // 这样在按钮变瘪的时候发生了意外,再次进入也可以显示用户期望的逻辑状态。  
  19.     this.isOpened = isOpened;  
  20.     postDelayed(new Runnable() {  
  21.         @Override  
  22.         public void run() {  
  23.             toggleSwitch(isOpened ? STATE_SWITCH_ON : STATE_SWITCH_OFF);  
  24.         }  
  25.     }, 300);  
  26. }  

替换公开方法 int getState() 和 setState(boolean newState)为:

[java] view plain copy
  1. /** 
  2.  * @return the state of switch view 
  3.  */  
  4. public boolean isOpened() {  
  5.     return isOpened;  
  6. }  
  7.   
  8. /** 
  9.  * if set true , the state change to on; 
  10.  * if set false, the state change to off 
  11.  * 
  12.  * @param isOpened 
  13.  */  
  14. public void setOpened(boolean isOpened) {  
  15.     refreshState(isOpened ? STATE_SWITCH_ON : STATE_SWITCH_OFF);  
  16. }  

关于如何储存状态 isOpened :

[java] view plain copy
  1. @Override  
  2. public Parcelable onSaveInstanceState() {  
  3.     Log.e("SwitchView""onSaveInstanceState:" + isOpened);  
  4.     Parcelable superState = super.onSaveInstanceState();  
  5.     SavedState ss = new SavedState(superState);  
  6.     ss.isOpened = isOpened;  
  7.     return ss;  
  8. }  
  9.   
  10. @Override  
  11. public void onRestoreInstanceState(Parcelable state) {  
  12.     SavedState ss = (SavedState) state;  
  13.     Log.e("SwitchView""onRestoreInstanceState:" + ss.isOpened);  
  14.     super.onRestoreInstanceState(ss.getSuperState());  
  15.     this.isOpened =  ss.isOpened;  
  16.     this.state = this.isOpened ? STATE_SWITCH_ON : STATE_SWITCH_OFF;  
  17. }  
  18.   
  19. static final class SavedState extends BaseSavedState {  
  20.     private boolean isOpened;  
  21.   
  22.     SavedState(Parcelable superState) {  
  23.         super(superState);  
  24.     }  
  25.   
  26.     private SavedState(Parcel in) {  
  27.         super(in);  
  28.         isOpened = 1 == in.readInt();  
  29.     }  
  30.   
  31.     @Override  
  32.     public void writeToParcel(Parcel out, int flags) {  
  33.         super.writeToParcel(out, flags);  
  34.         out.writeInt(isOpened ? 1 : 0);  
  35.     }  
  36. }  

这样就搞定了。如日志所打印,大家对应上面代码一定懂得。


其它的修改
1、删除了代码 case MotionEvent.ACTION_CANCEL: 
不删除的话在ListView之类的里面体验变成了这样 - - 。 


下拉刷新的时候会触发SwitchView的ACTION_CANCEL。 

这里是我喜欢把ACTION_UP和ACTION_CANCEL写一起的一个坏习惯把大家坑了~。

2、修改了接口OnStateChangedListener 

[java] view plain copy
  1. public interface OnStateChangedListener {  
  2.     void toggleToOn(View view);  
  3.   
  4.     void toggleToOff(View view);  
  5. }  
  6.   
  7. case MotionEvent.ACTION_UP:  
  8.     // 省略部分代码           
  9.     if (state == STATE_SWITCH_OFF2) {  
  10.         listener.toggleToOn(this);  
  11.     } else if (state == STATE_SWITCH_ON2) {  
  12.         listener.toggleToOff(this);  
  13.     }  
  14.     break;  
这样写的话多个SwitchView可以共用同一个回调。


至于阴影效果,如何使用的相关内容:


[逼格提升入口]戳我获得完整SwitchView源码[附含详细使用教程]


fork源码记得点赞[Star]哟,仔细阅读README.md文件。你会发现使用起来so easy。难度和学会使用CheckBox一样~


源码可能和本篇文章SwitchView如何诞生的攻略解说中部分代码不一致,以托管在github上源码为主。


源码中还有很多细节可以优化,比如颜色的设置,提供更加完善合理的接口等~  。 大家尝试自己秀秀操作添加吧。累了,就到这里

2016-08-28 01:22:44 lv_fq 阅读数 10085

最近根据项目需要,整理了一个相对比较全面的 WheelView 使用控件,借用之前看到的一句话来说,就是站在巨人肩膀上,进行了一些小调整。
这里先贴上效果图

这里写图片描述

一般常用的时间选择格式,,单项选择,以及城市联动,这里基本都可以满足了。

这里把 单项选择,和 日期时间选择 给提出到 Util 类中,代码如下:

public class Util {

    /**
     * 时间选择回调
     */
    public interface TimerPickerCallBack {
        void onTimeSelect(String date);
    }

    /**
     * 弹出时间选择
     *
     * @param context
     * @param type     TimerPickerView 中定义的 选择时间类型
     * @param format   时间格式化
     * @param callBack 时间选择回调
     */
    public static void alertTimerPicker(Context context, TimePickerView.Type type, final String format, final TimerPickerCallBack callBack) {
        TimePickerView pvTime = new TimePickerView(context, type);
        //控制时间范围
        //        Calendar calendar = Calendar.getInstance();
        //        pvTime.setRange(calendar.get(Calendar.YEAR) - 20, calendar.get(Calendar.YEAR));
        pvTime.setTime(new Date());
        pvTime.setCyclic(false);
        pvTime.setCancelable(true);
        //时间选择后回调
        pvTime.setOnTimeSelectListener(new TimePickerView.OnTimeSelectListener() {

            @Override
            public void onTimeSelect(Date date) {
//                        tvTime.setText(getTime(date));
                SimpleDateFormat sdf = new SimpleDateFormat(format);
                callBack.onTimeSelect(sdf.format(date));
            }
        });
        pvTime.setTextSize(16);
        //弹出时间选择器
        pvTime.show();
    }


    /**
     * 底部滚轮点击事件回调
     */
    public interface OnWheelViewClick {
        void onClick(View view, int postion);
    }

    /**
     * 弹出底部滚轮选择
     *
     * @param context
     * @param list
     * @param click
     */
    public static void alertBottomWheelOption(Context context, ArrayList<?> list, final OnWheelViewClick click) {

        final PopupWindow popupWindow = new PopupWindow();

        View view = LayoutInflater.from(context).inflate(R.layout.layout_bottom_wheel_option, null);
        TextView tv_confirm = (TextView) view.findViewById(R.id.btnSubmit);
        final WheelView wv_option = (WheelView) view.findViewById(R.id.wv_option);
        wv_option.setAdapter(new ArrayWheelAdapter(list));
        wv_option.setCyclic(false);
        wv_option.setTextSize(16);
        tv_confirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                popupWindow.dismiss();
                click.onClick(view, wv_option.getCurrentItem());
            }
        });

        view.findViewById(R.id.btnCancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // TODO: 2016/8/11 0011 取消
                popupWindow.dismiss();
            }
        });
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                int top = view.findViewById(R.id.ll_container).getTop();
                if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                    int y = (int) motionEvent.getY();
                    if (y < top) {
                        popupWindow.dismiss();
                    }
                }
                return true;
            }
        });
        popupWindow.setContentView(view);
        popupWindow.setOutsideTouchable(true);
        popupWindow.setFocusable(true);
        popupWindow.setBackgroundDrawable(new BitmapDrawable());
        popupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
        popupWindow.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
        popupWindow.showAtLocation(((ViewGroup) ((Activity) context).findViewById(android.R.id.content)).getChildAt(0), Gravity.CENTER, 0, 0);
    }
}
  • 单项选择

    这里是模拟传入 ArrayList 形式的 String 类型 :

// 单项选择
        for (int i = 0; i <= 10; i++) {
            mList.add("模拟数据" + i);
        }

        tv_single_option.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Util.alertBottomWheelOption(MainActivity.this, mList, new Util.OnWheelViewClick() {
                    @Override
                    public void onClick(View view, int postion) {
                        Toast.makeText(MainActivity.this, mList.get(postion), Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });

补充:我们实际项目中用法可能是传入一个实体对象,那么我们到 WheelView 中找到设置显示内容的方法:

/**
     * 根据传进来的对象反射出getPickerViewText()方法,来获取需要显示的值
     * @param item
     * @return
     */
    private String getContentText(Object item) {
        String contentText = item.toString();
        try {
            Class<?> clz = item.getClass();
            Method m = clz.getMethod(GETPICKERVIEWTEXT);
            contentText = m.invoke(item, new Object[0]).toString();
        } catch (NoSuchMethodException e) {
        } catch (InvocationTargetException e) {
        } catch (IllegalAccessException e) {
        } catch (Exception e){
        }
        return contentText;
    }

根据以上代码,可以看到如果是一个实体对象,那么就是通过对象内部定义的一个方法名为 GETPICKERVIEWTEXT(静态常量=”getPickerViewText”)的返回值来作为显示内容,

所以在创建对象的时候,要注意在对象内部添加一个 getPickerViewText()方法,代码如下:

public class TypeBean {

    private int id;
    private String name;

    public TypeBean(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //这个用来显示在PickerView上面的字符串,PickerView会通过反射获取getPickerViewText方法显示出来。
    public String getPickerViewText() {
        //这里还可以判断文字超长截断再提供显示
        return name;
    }
}
  • 日期选择
    这里是传入 选择日期类型,和 回调时间格式 就能直接得到想要的结果,
@Override
    public void onClick(View v) {
        String format = "";
        TimePickerView.Type type = null;
        switch (v.getId()) {
            case R.id.btn_ymdhm:
                type = TimePickerView.Type.ALL;
                format = "yyyy-MM-dd HH:mm";
                break;
            case R.id.btn_ymdh:
                type = TimePickerView.Type.YEAR_MONTH_DAY_HOUR;
                format = "yyyy-MM-dd HH";
                break;
            case R.id.btn_ymd:
                type = TimePickerView.Type.YEAR_MONTH_DAY;
                format = "yyyy-MM-dd";
                break;
            case R.id.btn_mdhm:
                type = TimePickerView.Type.MONTH_DAY_HOUR_MIN;
                format = "MM-dd HH:mm";
                break;
            case R.id.btn_hm:
                type = TimePickerView.Type.HOURS_MINS;
                format = "HH:mm";
                break;
            case R.id.btn_ym:
                type = TimePickerView.Type.YEAR_MONTH;
                format = "yyyy-MM";
                break;
        }
        Util.alertTimerPicker(this, type, format, new Util.TimerPickerCallBack() {
            @Override
            public void onTimeSelect(String date) {
                Toast.makeText(TestActivity.this, date, Toast.LENGTH_SHORT).show();
            }
        });

    }
  • 条件选择
private ArrayList<ProvinceBean> options1Items = new ArrayList<ProvinceBean>();
private ArrayList<ArrayList<String>> options2Items = new ArrayList<ArrayList<String>>();
private ArrayList<ArrayList<ArrayList<String>>> options3Items = new ArrayList<ArrayList<ArrayList<String>>>();

OptionsPickerView pvOptions;

private void showOptions(){
        //选项选择器
        pvOptions = new OptionsPickerView(this);
        // 初始化三个列表数据
        DataModel.initData(options1Items, options2Items, options3Items);

        //三级联动效果
        pvOptions.setPicker(options1Items, options2Items, options3Items, true);
        //设置选择的三级单位
//        pwOptions.setLabels("省", "市", "区");
        pvOptions.setTitle("选择城市");
        pvOptions.setCyclic(false, false, false);
        //设置默认选中的三级项目
        //监听确定选择按钮
        pvOptions.setSelectOptions(1, 1, 1);
        pvOptions.setTextSize(18);
        pvOptions.setOnoptionsSelectListener(new OptionsPickerView.OnOptionsSelectListener() {

            @Override
            public void onOptionsSelect(int options1, int option2, int options3) {
                //返回的分别是三个级别的选中位置
                String tx = options1Items.get(options1).getPickerViewText()
                        + options2Items.get(options1).get(option2)
                        + options3Items.get(options1).get(option2).get(options3);
                tvOptions.setText(tx);
                vMasker.setVisibility(View.GONE);
            }
        });
        //点击弹出选项选择器
        tvOptions.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                pvOptions.show();
            }
        });
    }

基本使用就这些了,也没什么技术含量,只是作为常用工具整理,也希望能给大家带来方便。

感谢一下提供该组件源码的大神。

Demo地址

2016-04-24 17:07:22 u012721519 阅读数 7336

在Android开发过程中,常常会因为感觉Android自带的Dialog的样式很丑,项目开发过程中会影响整体效果,会使得开发过程很是忧伤....(话唠时间结束!)

本文我将介绍一款开源的Dialog仿IOS底部弹窗效果IOS_Dialog_Library的使用。我将通过几个简单的示例介绍IOS_Dialog_Library.zip的使用方法。

1、IOS_Dialog_Library是开源的Dialog框架,所以首先你得下载IOS_Dialog_Library.zip包,并作为Library引入你的项目(引入过程省略)。

IOS_Dialog_Library下载地址:http://download.csdn.net/detail/u012721519/9500971


2、在创建完项目和引用完 IOS_Dialog_Library.zip 之后,开始编写代码。

activity_main.xml

<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"
    tools:context=".MainActivity"
    android:orientation="vertical">


    <Button
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="消息" />
    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="图片" />
    <Button
        android:id="@+id/btn3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="列表Item" />
    <Button
        android:id="@+id/btn4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="退出 弹窗" />
    <Button
        android:id="@+id/btn5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="错误提示窗口" />

</LinearLayout>

上面主要是5个Button,即演示五种不同的IOS_Dialog_Library的使用方法。


MainActivity.java

package example.com.showdialog;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import zhangphil.iosdialog.widget.ActionSheetDialog;
import zhangphil.iosdialog.widget.AlertDialog;

/**
 * package:example.com.showdialog
 * name:MainActivity.java
 * Write by Jimmy.li
 * Date:2016/4/24 16:39
 */
public class MainActivity extends Activity implements View.OnClickListener {
    private Button btn1, btn2, btn3, btn4, btn5;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);
        btn3 = (Button) findViewById(R.id.btn3);
        btn4 = (Button) findViewById(R.id.btn4);
        btn5 = (Button) findViewById(R.id.btn5);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
        btn3.setOnClickListener(this);
        btn4.setOnClickListener(this);
        btn5.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn1:
                new ActionSheetDialog(MainActivity.this)
                        .builder()
                        .setTitle("清空消息列表后,聊天记录依然保留,确定要清空消息列表?")
                        .setCancelable(true)
                        .setCanceledOnTouchOutside(true)
                        .addSheetItem("清空消息列表", ActionSheetDialog.SheetItemColor.Red
                                , new ActionSheetDialog.OnSheetItemClickListener() {
                            @Override
                            public void onClick(int which) {
                                //填写事件
                            }
                        }).show();
                break;
            case R.id.btn2:
                new ActionSheetDialog(MainActivity.this)
                        .builder()
                        .setCancelable(true)
                        .setCanceledOnTouchOutside(true)
                        .addSheetItem("发送给好友",
                                ActionSheetDialog.SheetItemColor.Blue,
                                new ActionSheetDialog.OnSheetItemClickListener() {
                                    @Override
                                    public void onClick(int which) {
                                        //填写事件
                                    }
                                })
                        .addSheetItem("转载到空间相册",
                                ActionSheetDialog.SheetItemColor.Blue,
                                new ActionSheetDialog.OnSheetItemClickListener() {
                                    @Override
                                    public void onClick(int which) {
                                        //填写事件
                                    }
                                })
                        .addSheetItem("上传到群相册",
                                ActionSheetDialog.SheetItemColor.Blue,
                                new ActionSheetDialog.OnSheetItemClickListener() {
                                    @Override
                                    public void onClick(int which) {
                                        //填写事件
                                    }
                                })
                        .addSheetItem("保存到手机",
                                ActionSheetDialog.SheetItemColor.Blue,
                                new ActionSheetDialog.OnSheetItemClickListener() {
                                    @Override
                                    public void onClick(int which) {
                                        //填写事件
                                    }
                                }).show();
                break;
            case R.id.btn3:
                new ActionSheetDialog(MainActivity.this)
                        .builder()
                        .setTitle("好友列表")
                        .setCancelable(true)
                        .setCanceledOnTouchOutside(true)
                        .addSheetItem("删除好友", ActionSheetDialog.SheetItemColor.Red
                                , new ActionSheetDialog.OnSheetItemClickListener() {
                            @Override
                            public void onClick(int which) {
                                //填写事件
                            }
                        })
                        .addSheetItem("增加好友", ActionSheetDialog.SheetItemColor.Blue
                                , new ActionSheetDialog.OnSheetItemClickListener() {
                            @Override
                            public void onClick(int which) {
                                //填写事件
                            }
                        })
                        .addSheetItem("备注", ActionSheetDialog.SheetItemColor.Blue
                                , new ActionSheetDialog.OnSheetItemClickListener() {
                            @Override
                            public void onClick(int which) {
                                //填写事件
                            }
                        }).show();
                break;
            case R.id.btn4:
                new AlertDialog(MainActivity.this)
                        .builder()
                        .setTitle("退出当前帐号")
                        .setMsg("再连续登陆天,就可变身为QQ达人。退出QQ可能会使你现有记录归零,确定退出?")
                        .setPositiveButton("确认退出", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                //填写事件
                            }
                        })
                        .setNegativeButton("取消", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                //填写事件
                            }
                        }).show();
                break;
            case R.id.btn5:
                new AlertDialog(MainActivity.this)
                        .builder()
                        .setTitle("错误信息")
                        .setMsg("你的手机sd卡出现问题,建议删除不需要的文件,否则收不到图片和视频等打文件")
                        .setPositiveButton("确定", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                //填写事件
                            }
                        }).show();
                break;
        }
    }
}


java代码主要是简要的说明了IOS_Dialog_Library的五种不同的实现方法及效果。可以在注释部分写点击事件。



3、运行效果图

         

运行效果图1                                   点击"消息"示意图2

         

点击"图片"效果图3                              点击"列表Item"效果图4

         

点击"退出弹窗"效果图5                           点击"错误提示窗口"图6


源码下载地址:http://download.csdn.net/detail/u012721519/9501050


Good luck!

Write by Jimmy.li












Android 仿IOS搜索框

阅读数 1824