編輯:關於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;
}
}
Android學習路線(五)開啟另一個Activity
在完成了 上一篇課程後,你已經有了一個應用。這個應用展示了一個包含一個文本框和一個按鈕的activity(一個單獨的界面)。在這次的課程中,你將會通過在MainActi
使用Android Studio查看Android Lollipop源碼
Android Studio作為Google的親兒子,Nexus手機系列所收到的待遇大家有目共睹.Android5.0出來之後,Nexus5第一時間就升級到了最新的系統.
Android提供的系統服務之--AudioManager(音頻管理器)
Android提供的系統服務之--AudioManager(音頻管理器)
Android中ListView + CheckBox實現單選、多選效果
還是先來看看是不是你想要的效果:不廢話,直接上代碼,很簡單,代碼裡都有注釋1 單選public class SingleActivity extends AppCompa