編輯:關於Android編程
讓用戶直接輸入身高體重,這種體驗真是太糟糕啦。我們不妨讓用戶啟動手指滑動標尺來確定他的身高體重,這樣不是更有趣麼?
package com.lw.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; import android.support.annotation.IntegerRes; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.widget.OverScroller; import com.lw.R; /** * 標尺類 */ public class RulerView extends View { //getSimpleName()返回源代碼中給出的底層類的簡稱。 final String TAG = RulerView.class.getSimpleName(); //開始范圍 private int mBeginRange; //結束范圍 private int mEndRange; /**內部寬度,也就是標尺每條的寬度*/ private int mInnerWidth; //標尺條目間的間隔 private int mIndicatePadding; //顯示的畫筆 private Paint mIndicatePaint; //文字畫筆 private Paint mTextPaint; //顯示的寬度 private int mIndicateWidth; //顯示的大小 private float mIndicateScale; //最後的手勢的X坐標 private int mLastMotionX; /**是否可以滑動*/ private boolean mIsDragged; //是否自動匹配 private boolean mIsAutoAlign = true; //是否需要顯示文字 private boolean mIsWithText = true; //文字顏色 private int mTextColor; //文字大小 private float mTextSize; //標尺的顏色 private int mIndicateColor; //大小比例監聽器 private OnScaleListener mListener; //標尺條顯示的位置:top,bottom private int mGravity; /**標尺矩形(刻度條)*/ private Rect mIndicateLoc; /**滾動相關參數,這個類封裝了滾動與超能力的界限*/ private OverScroller mOverScroller; /**幫助跟蹤觸摸事件的速度,用於執行投擲等動作。*/ private VelocityTracker mVelocityTracker; /**觸摸溢出*/ private int mTouchSlop; //最小滑動速率 private int mMinimumVelocity; //最大速率 private int mMaximumVelocity; public RulerView(Context context) { this(context, null); } public RulerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 最終都是調用此構造方法 * * @param context * @param attrs * @param defStyleAttr */ public RulerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //獲取自定義屬性數據集,並寫入缺省值,自定義了8個屬性 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RulerView); mIndicateColor = ta.getColor(R.styleable.RulerView_indicateColor, Color.BLACK); mTextColor = ta.getColor(R.styleable.RulerView_textColor, Color.GRAY); mTextSize = ta.getDimension(R.styleable.RulerView_textSize, 18); mBeginRange = ta.getInt(R.styleable.RulerView_begin, 0); mEndRange = ta.getInt(R.styleable.RulerView_end, 100); //標尺寬度 mIndicateWidth = (int) ta.getDimension(R.styleable.RulerView_indicateWidth, 5); //標尺的間隙 mIndicatePadding = (int) ta.getDimension(R.styleable.RulerView_indicatePadding, 15); ta.recycle(); //標尺條顯示的位置,缺省值為顯示在底部 int[] indices = new int[]{android.R.attr.gravity}; ta = context.obtainStyledAttributes(attrs, indices); mGravity = ta.getInt(ta.getIndex(0), Gravity.BOTTOM); ta.recycle(); //默認顯示比例為0.7倍 mIndicateScale = 0.7f; initValue(); } /** * 初始化數值 */ private void initValue() { /** 創建這個滾動類,並設置滾動模式為:1.OVER_SCROLL_ALWAYS 標准模式 * 還有兩種滾動模式為:2.OVER_SCROLL_IF_CONTENT_SCROLLS 內容滾動 * 3.OVER_SCROLL_NEVER 不滾動 */ mOverScroller = new OverScroller(getContext()); setOverScrollMode(OVER_SCROLL_ALWAYS); //獲取視圖配置,設置觸摸溢出,和最小和最大的觸摸速率 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); //設置標尺的畫筆,實心畫 mIndicatePaint = new Paint(); mIndicatePaint.setStyle(Paint.Style.FILL); //設置文字畫筆,實心畫,並消除鋸齒 mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setStyle(Paint.Style.FILL); mTextPaint.setColor(mTextColor); mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setTextSize(mTextSize); //內部寬度(標尺結束范圍-標尺開始范圍)*指示寬度 mInnerWidth = (mEndRange - mBeginRange) * getIndicateWidth(); //標尺定位為一個矩形 mIndicateLoc = new Rect(); } /** * 重寫繪制方法 * * @param canvas */ @Override protected void onDraw(Canvas canvas) { /** * 當我們對畫布進行旋轉,縮放,平移等操作的時候其實我們是想對特定的元素進行操作, * 比如圖片,一個矩形等,但是當你用canvas的方法來進行這些操作的時候, * 其實是對整個畫布進行了操作,那麼之後在畫布上的元素都會受到影響, * 所以我們在操作之前調用canvas.save()來保存畫布當前的狀態, * 當操作之後取出之前保存過的狀態,這樣就不會對其他的元素進行影響 */ int count = canvas.save(); //循環繪制標尺條(刻度),根據最大值和最小值來繪制 for (int value = mBeginRange, position = 0; value <= mEndRange; value++, position++) { drawIndicate(canvas, position); //如果需要數字,還需要在刻度下繪制數字 if (mIsWithText) drawText(canvas, position, String.valueOf(value)); } //恢復Canvas的狀態 canvas.restoreToCount(count); } /** * 繪制標尺條(刻度),0到100就會顯示100個刻度 * @param canvas 畫布 * @param position */ private void drawIndicate(Canvas canvas, int position) { computeIndicateLoc(mIndicateLoc, position); int left = mIndicateLoc.left + mIndicatePadding; int right = mIndicateLoc.right - mIndicatePadding; int top = mIndicateLoc.top; int bottom = mIndicateLoc.bottom; if (position % 5 != 0) { int indicateHeight = bottom - top; if (isAlignTop()) { bottom = (int) (top + indicateHeight * mIndicateScale); } else { top = (int) (bottom - indicateHeight * mIndicateScale); } } mIndicatePaint.setColor(mIndicateColor); canvas.drawRect(left, top, right, bottom, mIndicatePaint); } /** * 繪制文字,每5個刻度繪制一個文字用於提示 * @param canvas * @param position * @param text */ private void drawText(Canvas canvas, int position, String text) { if (position % 5 != 0) return; computeIndicateLoc(mIndicateLoc, position); int textHeight = computeTextHeight(); mTextPaint.setColor(mTextColor); mTextPaint.setTextSize(mTextSize); mTextPaint.setTextAlign(Paint.Align.CENTER); int x = (mIndicateLoc.left + mIndicateLoc.right) / 2; int y = mIndicateLoc.bottom + textHeight; if (!isAlignTop()) { y = mIndicateLoc.top; mTextPaint.getTextBounds(text, 0, text.length(), mIndicateLoc); y += mIndicateLoc.top / 2; //增加一些偏移 } canvas.drawText(text, x, y, mTextPaint); } /** * 計算指示器的位置:設置左上右下 * 最終是設置了此矩形(刻度的左上右下) * @param outRect 矩形 * @param position 位置數值(代表第幾個刻度) */ private void computeIndicateLoc(Rect outRect, int position) { if (outRect == null) return; int height = getHeight(); int indicate = getIndicateWidth(); int left = (indicate * position); int right = left + indicate; int top = getPaddingTop();//獲得當前View的頂內距 int bottom = height - getPaddingBottom();//視圖高度-視圖低內距 if (mIsWithText) { int textHeight = computeTextHeight(); if (isAlignTop()) bottom -= textHeight;//如果是刻度顯示在頂部,底部要減去文字的高度 else top += textHeight;//如果是刻度顯示在底部,頂部要加上文字的高度 } //文字偏移量,左邊和右邊都加上一個偏移量 int offsets = getStartOffsets(); left += offsets; right += offsets; outRect.set(left, top, right, bottom); } /** * 開始偏移,如果要包含文字的話才需要偏移。 * * @return */ private int getStartOffsets() { if (mIsWithText) { String text = String.valueOf(mBeginRange); //返回文字的寬度 int textWidth = (int) mTextPaint.measureText(text, 0, text.length()); return textWidth / 2;//實際偏移文字寬度的一半,使其居中顯示 } return 0; } /** * 觸摸相關事件 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { //如果不存在初始速度跟蹤 initVelocityTrackerIfNotExists(); //速度追蹤者 添加移動事件 mVelocityTracker.addMovement(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //按下時如果滑動還沒結束 if (mIsDragged = !mOverScroller.isFinished()) { if (getParent() != null) //要求禁止攔截觸摸事件 getParent().requestDisallowInterceptTouchEvent(true); } //如果動畫沒結束,就結束動畫 if (!mOverScroller.isFinished()) mOverScroller.abortAnimation(); //記錄按下的x的坐標 mLastMotionX = (int) event.getX(); return true; case MotionEvent.ACTION_MOVE: //移動時x的值,並得到(按下x值-移動x)值的差值 int curX = (int) event.getX(); int deltaX = mLastMotionX - curX; //如果滑動未結束,且移動距離的絕對值大於觸摸溢出量 if (!mIsDragged && Math.abs(deltaX) > mTouchSlop) { if (getParent() != null) //如果有父級控件,就告訴父級控件不要攔截我的觸摸事件 getParent().requestDisallowInterceptTouchEvent(true); //並設置滑動結束 mIsDragged = true; //如果觸摸差值》0,觸摸差值需要-觸摸溢出量,否則加上 if (deltaX > 0) { deltaX -= mTouchSlop; } else { deltaX += mTouchSlop; } } //如果滑動結束,最後的x坐標就是當前觸摸的的點 if (mIsDragged) { mLastMotionX = curX; //如果滾動的X值《0或者大於最大的滾動值了,讓觸摸差值*0.7 if (getScrollX() <= 0 || getScrollX() >= getMaximumScroll()) deltaX *= 0.7; //滾動超出正常的標准行為的視圖,速率監聽清除????????????? if (overScrollBy(deltaX, 0, getScrollX(), getScrollY(), getMaximumScroll(), 0, getWidth(), 0, true)) { mVelocityTracker.clear(); } } break; case MotionEvent.ACTION_UP: { if (mIsDragged) { //檢查滑動的速度,1000單位 mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); //獲得X軸上的流速 int initialVelocity = (int) mVelocityTracker.getXVelocity(); //如果x軸流速》最小流速 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { fling(-initialVelocity); } else { //alignCenter(); //回彈到末尾 sprintBack(); } } //滑動結束 mIsDragged = false; //釋放追蹤器資源 recycleVelocityTracker(); break; } case MotionEvent.ACTION_CANCEL: { //如果滑動結束,且滾動結束,就回滾 if (mIsDragged && mOverScroller.isFinished()) { sprintBack(); } mIsDragged = false; recycleVelocityTracker(); break; } } return true; } /** * 刷新參數值 */ private void refreshValues() { //內部寬度 = (最大值-開始值)*刻度寬度 mInnerWidth = (mEndRange - mBeginRange) * getIndicateWidth(); invalidateView(); } /** * 最終指示寬度 :刻度寬度+刻度內邊距+刻度內邊距 * * @return */ private int getIndicateWidth() { return mIndicateWidth + mIndicatePadding + mIndicatePadding; } /** * 獲取最小滾動值。 * * @return */ private int getMinimumScroll() { return -(getWidth() - getIndicateWidth()) / 2 + getStartOffsets(); } /** * 獲取最大滾動值。 * * @return */ private int getMaximumScroll() { return mInnerWidth + getMinimumScroll(); } /** * 調整刻度,使其居中。 */ private void adjustIndicate() { if (!mOverScroller.isFinished()) mOverScroller.abortAnimation(); int position = computeSelectedPosition(); int scrollX = getScrollByPosition(position); scrollX -= getScrollX(); if (scrollX != 0) { //滾動邊界開始滾動 mOverScroller.startScroll(getScrollX(), getScrollY(), scrollX, 0); invalidateView(); } } /** * 投擲 * @param velocityX * 根據x軸滑動速率,來回退刷新界面 */ public void fling(int velocityX) { mOverScroller.fling(getScrollX(), getScrollY(), velocityX, 0, getMinimumScroll(), getMaximumScroll(), 0, 0, getWidth() / 2, 0); invalidateView(); } /** * 回彈 */ public void sprintBack() { mOverScroller.springBack(getScrollX(), getScrollY(), getMinimumScroll(), getMaximumScroll(), 0, 0); invalidateView(); } public void setOnScaleListener(OnScaleListener listener) { if (listener != null) { mListener = listener; } } /** * 獲取position的絕對滾動位置。 * * @param position * @return */ private int getScrollByPosition(int position) { computeIndicateLoc(mIndicateLoc, position); int scrollX = mIndicateLoc.left - getStartOffsets() + getMinimumScroll(); return scrollX; } /** * 計算當前已選擇的位置 * * @return */ public int computeSelectedPosition() { //計算出兩個刻度的中間位置 int centerX = getScrollX() - getMinimumScroll() + getIndicateWidth() / 2; //通過中間位置來判斷選擇的刻度值位置 centerX = Math.max(0, Math.min(mInnerWidth, centerX)); int position = centerX / getIndicateWidth(); return position; } /** * 平滑滾動 * @param position */ public void smoothScrollTo(int position) { //如果選擇的位置<0或者開始值+選擇位置大於最終值,就直接返回吧 if (position < 0 || mBeginRange + position > mEndRange) return; //如果滾動沒有完成,中斷它的動畫吧 if (!mOverScroller.isFinished()) mOverScroller.abortAnimation(); int scrollX = getScrollByPosition(position); mOverScroller.startScroll(getScrollX(), getScrollY(), scrollX - getScrollX(), 0); invalidateView(); } /** * 平滑滾動到的值 * @param value */ public void smoothScrollToValue(int value) { int position = value - mBeginRange; smoothScrollTo(position); } /** * 觸發放大縮小事件 * @param scale */ private void onScaleChanged(int scale) { if (mListener != null) mListener.onScaleChanged(scale); } /** * 重新在滾動時的事件 * @param scrollX * @param scrollY * @param clampedX 固定的x * @param clampedY 固定的Y */ @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { //如果滾動沒有完成,設置滾動x參數,並監聽滾動 if (!mOverScroller.isFinished()) { final int oldX = getScrollX(); final int oldY = getScrollY(); setScrollX(scrollX); onScrollChanged(scrollX, scrollY, oldX, oldY); if (clampedX) { //sprintBack(); } } else { super.scrollTo(scrollX, scrollY); } //如果監聽器不為null,賦值當前選擇的位置,並觸發縮放改變事件 if (mListener != null) { int position = computeSelectedPosition(); onScaleChanged(position + mBeginRange); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 計算文字高度 * @return */ private int computeTextHeight() { //使用FontMetrics對象,計算文字的坐標。 Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); float textHeight = fontMetrics.descent - fontMetrics.ascent; return (int) textHeight; } private boolean isAlignTop() { //&為位運算符,就是32位二進制值得比較 return (mGravity & Gravity.TOP) == Gravity.TOP; } public void setGravity(int gravity) { this.mGravity = gravity; invalidateView(); } /** * 計算滾動 */ @Override public void computeScroll() { if (mOverScroller.computeScrollOffset()) { int oldX = getScrollX(); int oldY = getScrollY(); // 返回滾動中的電流偏移量,百度居然這麼翻譯 int x = mOverScroller.getCurrX(); int y = mOverScroller.getCurrY(); //滾動過多得操作 overScrollBy(x - oldX, y - oldY, oldX, oldY, getMaximumScroll(), 0, getWidth(), 0, false); invalidateView(); } else if (!mIsDragged && mIsAutoAlign) {//如果不再滾動且開啟了自動對齊 adjustIndicate(); } } @Override protected int computeHorizontalScrollRange() { return getMaximumScroll(); } /** * 刷新界面 * 如果版本大於16(4.1) * 使用postInvalidate可以直接在線程中更新界面 * invalidate()必須在UI線程中使用 */ public void invalidateView() { if (Build.VERSION.SDK_INT >= 16) { postInvalidateOnAnimation(); } else invalidate(); } /** * 獲得周轉率追蹤器 */ private void initVelocityTrackerIfNotExists() { if (mVelocityTracker == null) { //獲得當前周轉率追蹤 mVelocityTracker = VelocityTracker.obtain(); } } /** * 釋放 周轉率追蹤器資源 */ private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } /** * 放大縮小監聽接口 */ public interface OnScaleListener { void onScaleChanged(int scale); } /** * 設置刻度的寬度 * @param indicateWidth */ public void setIndicateWidth(@IntegerRes int indicateWidth) { this.mIndicateWidth = indicateWidth; refreshValues(); } /** * 設置刻度內間距 * @param indicatePadding */ public void setIndicatePadding(@IntegerRes int indicatePadding) { this.mIndicatePadding = indicatePadding; refreshValues(); } public void setWithText(boolean withText) { this.mIsWithText = withText; refreshValues(); } public void setAutoAlign(boolean autoAlign) { this.mIsAutoAlign = autoAlign; refreshValues(); } /** * 是否顯示文字 * @return */ public boolean isWithText() { return mIsWithText; } /** * 自動對齊刻度 * @return */ public boolean isAutoAlign() { return mIsAutoAlign; } }
在完成了 上一篇課程後,你已經有了一個應用。這個應用展示了一個包含一個文本框和一個按鈕的activity(一個單獨的界面)。在這次的課程中,你將會通過在MainActi
Android Studio作為Google的親兒子,Nexus手機系列所收到的待遇大家有目共睹.Android5.0出來之後,Nexus5第一時間就升級到了最新的系統.
Android提供的系統服務之--AudioManager(音頻管理器)
還是先來看看是不是你想要的效果:不廢話,直接上代碼,很簡單,代碼裡都有注釋1 單選public class SingleActivity extends AppCompa