仿小米计算器

模仿小米计算器:

基本的功能:大写转换,科学计算器(打开自带Calculator),长度转换,面积转换,体积转换,温度转换,速度转换,时间转换,重量转换。

最终效果图:

仿小米计算器_第1张图片

仿小米计算器_第2张图片

仿小米计算器_第3张图片

仿小米计算器_第4张图片

实现中遇到的问题:

GridView高度自适应全屏。

自定义PickerView。

单位转换的算法设计。

AlertDialog位置可控。

因为写这个比较早了,现在想想可以优化的点:

用RecyclerView来替代GridView。

用PopupWindow来代替AlertDialog。

自定义PickerView还可以加上惯性滑动。

自定义PickerView代码如下:

package com.example.liuunitconvert;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import android.animation.ArgbEvaluator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontMetricsInt;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

/**
 * 
 * @author lqp
 * 
 */
public class PickerView extends View {
	// 可编辑属性
	private int num = 7;// view中显示数据项个数
	private float minTextSize = 20;// 文字最小
	private float maxTextSize = 120;// 文字最大
	private int textColor = Color.BLACK;// 字体颜色
	private int centerTextColor = Color.BLUE;// 选中框的文字颜色
	private int otherPartColor = Color.WHITE;// 其他部分的颜色
	private int lineColor = Color.BLACK;// 分割线颜色
	private float speed = 0.5f;// 手指抬起后是继续前进还是回退的速度
	private float devideLineLength = 300;// 设置分割线长度,2×devideLineLength
	// 成员变量
	double zz;
	private Handler mHandler;
	private TimerTask mTask;
	private Timer mTimer;
	private float mCurrentLocation;// 当前view竖立中心的Text对应位置(浮点数),用于动态设置字号大小,透明度,渐变字体颜色
	private int mCurrentItem;
	private int mCurrentPosition;// 当前view竖立中心选中框的内容对应ArrayList的postion,用于返回,与集合奇偶,mCurrentItem有关
	private float itemError;// 当手指抬起后,最近的item距离选框中心线的误差,用于继续绘画出继续前进或者回退的动画
	private ArrayList strings = new ArrayList();// 显示的数据集合
	private int midStringNum;// 默认选中数据集合的中间项
	private float viewHeight;
	private float viewWidth;
	private float itemHeight;// 单项的高度,view的高度/num
	private Paint mPaint;
	private float offset = 0;// 滑动距离,相对与view的竖立中心线
	private float x = 0;
	private float y = 0;
	private ArgbEvaluator evaluator;

	float positionOffset;// 大小在0~1 用于计算varible的各项属性
	private boolean isSetChecked=false;//是否调用了setChecked方法
	private int position;//调用了setChecked方法后,设置positon
	private int varibleTextAlpha;// 与offset,minTextSize,maxTextSize关联的字体透明度
	private float varibleTextSize;// 与offset,minTextSize,maxTextSize关联的字体大小
	private int varibleTextColor;// 与offset,minTextSize,maxTextSize关联的字体颜色

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

	public PickerView(Context context, AttributeSet attrs) {
		super(context, attrs);
//		strings.add("liu0");
//		strings.add("liu1");
//		strings.add("liu2");
//		strings.add("liu3");
//		strings.add("liu4");
//		strings.add("liu5");
//		strings.add("liu6");
//		strings.add("liu7");
//		strings.add("liu8");
//		strings.add("liu9");
//		strings.add("liu10");
//		// Collections.reverse(strings);
		minTextSize = DensityUtil.dip2px(context,9);
		maxTextSize =  DensityUtil.dip2px(context, 18);
		devideLineLength = DensityUtil.dip2px(context,100);
		centerTextColor=context.getResources().getColor(R.color.picker_center_text);
		otherPartColor=context.getResources().getColor(R.color.picker_other_parts);
		lineColor=context.getResources().getColor(R.color.picker_line_color);
		/**
		 * 总个数为奇数:显示中间一个,比如总数3个,3/2=1。list.get(1)实际对应的第二个元素,所以在中间
		 * 总个数为偶数:上部分要比下部分多显示一个
		 */
		mCurrentPosition=strings.size() / 2;
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mTimer = new Timer();
		mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				if (Math.abs(offset - (mCurrentItem - midStringNum)
						* itemHeight)
						/ itemHeight > 0.001) {
					offset = offset + (itemError) / (20/speed);
					mCurrentLocation = offset / itemHeight + midStringNum;
					invalidate();
				}
			}
		};
	}

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

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		viewHeight = getMeasuredHeight();
		viewWidth = getMeasuredWidth();
		itemHeight = viewHeight / num;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		super.onDraw(canvas);
		/**
		 * 在我们设置调用setChecked后,因为Activity中虽然获取了PickerView的实例,但是仅仅是执行了构造方法。
		 * 构造方法里面是获取不到view的宽,高的。
		 * view的宽高只有去onMeasure后才能获取到。
		 * 所以有关于itemHeight的计算只能放置到onDraw里面来。
		 */
		if(isSetChecked){
		offset = -(position - midStringNum) * itemHeight;
		mCurrentPosition=position;
		isSetChecked=false;
		}
		midStringNum = strings.size() / 2;
		mCurrentItem = midStringNum + Math.round(offset / itemHeight);
		mCurrentLocation = midStringNum + offset / itemHeight;
		mPaint.setTextAlign(Align.CENTER);
		mPaint.setColor(lineColor);
		mPaint.setStrokeWidth(3);
		// 画两根线
		this.setBackgroundColor(getResources().getColor(R.color.picker_bg_color));
		canvas.drawLine(0, viewHeight / 2
				- itemHeight / 2, viewWidth, viewHeight
				/ 2 - itemHeight / 2, mPaint);
		canvas.drawLine(0, viewHeight / 2
				+ itemHeight / 2, viewWidth, viewHeight
				/ 2 + itemHeight / 2, mPaint);
		mPaint.setColor(Color.WHITE);
		canvas.drawRect(0, viewHeight
				/ 2 - itemHeight / 2,  viewWidth, viewHeight
				/ 2+ itemHeight / 2, mPaint);
		/**
		 * 画文字 计算字体颜色 计算字体大小
		 */
		float colorMult;
		float changeSpeed=1.5f;
		for (int i = 0; i <= strings.size() - 1; i++) {
			evaluator = new ArgbEvaluator();
			if (strings.size() % 2 == 0) {
				positionOffset = (float) (1 / (Math.pow(
						Math.abs(strings.size() - i - mCurrentLocation), 2) + 1));
				colorMult=1-changeSpeed*Math.abs(strings.size() - i - mCurrentLocation);
			} else {
				positionOffset = (float) (1 / (Math.pow(
						Math.abs(strings.size() - 1 - i - mCurrentLocation), 2) + 1));
				colorMult=1-changeSpeed*Math.abs(strings.size() - 1-i - mCurrentLocation);
			}
			if (i==mCurrentPosition) {
				varibleTextColor = (Integer) evaluator.evaluate(colorMult,
						otherPartColor, centerTextColor);
			}
			else{
				varibleTextColor=otherPartColor;
			}
			mPaint.setColor(varibleTextColor);
			varibleTextSize = (float) (minTextSize + (maxTextSize - minTextSize)
					* positionOffset);
			mPaint.setAlpha((int) (255*positionOffset));
			mPaint.setTextSize(varibleTextSize);
			FontMetricsInt newfmi = mPaint.getFontMetricsInt();
			float newbaseline = (float) (viewHeight / 2 - (newfmi.bottom / 2.0 + newfmi.top / 2.0));
			canvas.drawText(strings.get(i), viewWidth / 2, newbaseline
					+ itemHeight * (i - midStringNum) + offset, mPaint);
		}

	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub

		switch (event.getActionMasked()) {
		case MotionEvent.ACTION_DOWN:
			x = event.getX();
			y = event.getY();
			/**
			 * 注意:这三行非常重要 不能重复schedule同一个任务,否则会报错
			 * 这里的代码配合ACTION_UP里面的代码。实现了每次up的时候计时器开始干事情,帮助回滚或者继续滑动。
			 * 而在ACTION_DOWN的时候就让计时器停下。
			 * 所以在ACTION_MOVE的时候是没有计时器的程序执行来重绘的,只是ACTION_MOVE里面的代码在重绘。
			 */
			if (mTask != null) {
				mTask.cancel();
				mTask = null;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			offset = offset + event.getY() - y;
			y = event.getY();
			invalidate();
			/**
			 * 设定滑动的边界,要分两种情况 集合元素个数为奇数和为偶数
			 */
			int index = strings.size() / 2;// 别把这行直接加进去,否则奇数时会多出0.5
			float bound = (float) (itemHeight * index);
			if (strings.size() % 2 == 1) {
				if (Math.abs(offset) > bound) {
					if (offset > 0) {
						offset = bound;
					} else {
						offset = -bound;
					}
				}
			} else {
				if (offset > bound) {
					offset = bound;
				}
				if (offset < -bound + itemHeight) {
					offset = -bound + itemHeight;
				}
			}
			mCurrentLocation = offset / itemHeight + midStringNum;
			/**
			 * 下面这两行同时在ACTION_DOWN里面设置的理由:
			 * 有可能两个手指在操作,一个手指滑动pickerview(未抬起,也就是没有触发ACTION_UP),另一个手指点击button并返回pickerview.getCurrentPosition.
			 * 为了返回准确的currentpositon,这里也要进行设置
			 */
			mCurrentItem = Math.round(offset / itemHeight) + midStringNum;
			mCurrentPosition = midStringNum - (mCurrentItem - midStringNum);
			break;
		case MotionEvent.ACTION_UP:
			invalidate();
			/**
			 * 注意点: 1.当你手指下滑的时候,如果从中心线来算,你的偏移值是正的。重绘text的时候
			 * canvas.drawText(String text, float x, float y, Paint paint)
			 * y值是加上offset. 假设你是a1,a2----a10向下排列,你向下偏移了两个位置。
			 * 如果原来中心显示的是a5,那么现在显示的不是a7,而是a3;而a5也到了原来a7的位置。
			 * 所以获取正确的ArrayList集合的对应位置用下面代码
			 */
			mCurrentItem = Math.round(offset / itemHeight) + midStringNum;// 判断当前应该是在哪个位置,然后滑动到这个位置
			mCurrentPosition = midStringNum - (mCurrentItem - midStringNum);// 将偏差数反向就可以了
			Log.e("current", "text:" + strings.get(mCurrentPosition)
					+ "\n mCurrentPosition" + mCurrentPosition);
			itemError = (Math.round(offset / itemHeight)) * itemHeight - offset;
			mTask = new TimerTask() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					mHandler.sendEmptyMessage(0);
				}
			};
			mTimer.schedule(mTask, 0, 10);

			break;
		default:
			mCurrentItem = Math.round(offset / itemHeight) + midStringNum;
			mCurrentPosition = midStringNum - (mCurrentItem - midStringNum);
			if (mTask != null) {
				mTask.cancel();
				mTask = null;
			}
			break;
		}

		return true;
	}

	@Override
	protected void onDetachedFromWindow() {
		// TODO Auto-generated method stub
		super.onDetachedFromWindow();
		mTimer.cancel();
	}

	public void setData(ArrayList dataList) {
		strings = dataList;
		midStringNum = strings.size() / 2;
		mCurrentItem = midStringNum;
		mCurrentLocation = midStringNum;
		offset = 0;
		invalidate();
	}

	public void setChecked(int position) {
		isSetChecked=true;
		this.position=position;
		invalidate();
	}
	public int getCurrentPosition(){
		/**
		 * 当前位置
		 * 1.没设置具体位置时,在构造方法里面设置为ArrayList中间postion
		 * 2.设置具体位置后,当前位置为设置的position
		 * 3.每次手指滑动抬起(准确说是除了手指按下和移动外的其他所有MotionEvent类型)后,position位置改变
		 * 
		 */
		if(isSetChecked){
			mCurrentPosition=position;
		}
		return mCurrentPosition;
	}
}

完整源代码:

http://download.csdn.net/detail/mis_wenwen/9875459

也可以留言我发给你。



你可能感兴趣的:(仿小米计算器)