編輯:關於Android編程
在網上看到了一個IOS組件PendulumView,實現了鐘擺的動畫效果。由於原生的進度條確實是不好看,所以想可以自定義View實現這樣的效果,以後也可以用於加載頁面的進度條。
廢話不多說,先上效果圖
底部黑邊是錄制時不小心錄上的,可以忽略。
既然是自定義View我們就按標准的流程來,第一步,自定義屬性
在Android項目的res->values目錄下新建一個attrs.xml文件,文件內容如下:
其中declare-styleable的name屬性用於在代碼中引用該屬性文件。name屬性,一般情況下寫的都是我們自定義View的類名,較為直觀。
使用styleale,系統可以為我們完成很多常量(int[]數組,下標常量)等的編寫,簡化我們的開發工作,例如下面代碼中用到的R.styleable.PendulumView_golbeNum等就是系統為我們自動生成的。
globeNum屬性表示小球數量,globeColor表示小球顏色,globeRadius表示小球半徑,swingRadius表示擺動半徑
在自定view的構造方法中通過TypedArray讀取屬性值
通過AttributeSet同樣可以獲取屬性值,但是如果屬性值是引用類型,則得到的只是ID,仍需繼續通過解析ID獲取真正的屬性值,而TypedArray直接幫助我們完成了上述工作。
public PendulumView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //使用TypedArray讀取自定義的屬性值 TypedArray ta = context.getResources().obtainAttributes(attrs, R.styleable.PendulumView); int count = ta.getIndexCount(); for (int i = 0; i < count; i++) { int attr = ta.getIndex(i); switch (attr) { case R.styleable.PendulumView_globeNum: mGlobeNum = ta.getInt(attr, 5); break; case R.styleable.PendulumView_globeRadius: mGlobeRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics())); break; case R.styleable.PendulumView_globeColor: mGlobeColor = ta.getColor(attr, Color.BLUE); break; case R.styleable.PendulumView_swingRadius: mSwingRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics())); break; } } ta.recycle(); //避免下次讀取時出現問題 mPaint = new Paint(); mPaint.setColor(mGlobeColor); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //高度為小球半徑+擺動半徑 int height = mGlobeRadius + mSwingRadius; //寬度為2*擺動半徑+(小球數量-1)*小球直徑 int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius; //如果測量模式為EXACTLY,則直接使用推薦值,如不為EXACTLY(一般處理wrap_content情況),使用自己計算的寬高 setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height); }其中
int height = mGlobeRadius + mSwingRadius;
int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
用於處理測量模式為AT_MOST的情況,一般是自定義View的寬高設置為了wrap_content,此時通過小球的數量,半徑,擺動的半徑等計算View的寬高,如下圖:
以小球個數5為例,View的大小為下圖紅色矩形區域
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //繪制除左右兩個小球外的其他小球 for (int i = 0; i < mGlobeNum - 2; i++) { canvas.drawCircle(mSwingRadius + (i + 1) * 2 * mGlobeRadius, mSwingRadius, mGlobeRadius, mPaint); } if (mLeftPoint == null || mRightPoint == null) { //初始化最左右兩小球坐標 mLeftPoint = new Point(mSwingRadius, mSwingRadius); mRightPoint = new Point(mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1), mSwingRadius); //開啟擺動動畫 startPendulumAnimation(); } //繪制左右兩小球 canvas.drawCircle(mLeftPoint.x, mLeftPoint.y, mGlobeRadius, mPaint); canvas.drawCircle(mRightPoint.x, mRightPoint.y, mGlobeRadius, mPaint); }
onDraw()方法是自定義View的關鍵所在,在該方法體內繪制View的顯示效果。代碼首先繪制了除去最左邊最右邊小球以外的其他小球,然後對左右兩小球的坐標值進行判斷,如果是第一次繪制,坐標值均為空,則初始化兩小球坐標,並且開啟動畫。最後通過mLeftPoint,mRightPoint的x,y值,繪制左右兩個小球。
其中mLeftPoint,mRightPoint均是android.graphics.Point對象,僅是使用它們來存放左右兩小球的x,y坐標信息。
public void startPendulumAnimation() { //使用屬性動畫 final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { //參數fraction用於表示動畫的完成度,我們根據它來計算當前的動畫值 double angle = Math.toRadians(90 * fraction); int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle)); int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle)); Point point = new Point(x, y); return point; } }, new Point(), new Point()); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Point point = (Point) animation.getAnimatedValue(); //獲得當前的fraction值 float fraction = anim.getAnimatedFraction(); //判斷是否是fraction先減小後增大,即是否處於即將向上擺動狀態 //在每次即將向上擺動時切換小球 if (lastSlope && fraction > mLastFraction) { isNext = !isNext; } //通過不斷改動左右小球的x,y坐標值實現動畫效果 //利用isNext來判斷應該是左邊小球動,還是右邊小球動 if (isNext) { //當左邊小球擺動時,右邊小球置於初始位置 mRightPoint.x = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1); mRightPoint.y = mSwingRadius; mLeftPoint.x = mSwingRadius - point.x; mLeftPoint.y = mGlobeRadius + point.y; } else { //當右邊小球擺動時,左邊小球置於初始位置 mLeftPoint.x = mSwingRadius; mRightPoint.y = mSwingRadius; mRightPoint.x = mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + point.x; mRightPoint.y = mGlobeRadius + point.y; } invalidate(); lastSlope = fraction < mLastFraction; mLastFraction = fraction; } }); //設置永久循環播放 anim.setRepeatCount(ValueAnimator.INFINITE); //設置循環模式為倒序播放 anim.setRepeatMode(ValueAnimator.REVERSE); anim.setDuration(200); //設置補間器,控制動畫的變化速率 anim.setInterpolator(new DecelerateInterpolator()); anim.start(); }其中使用ValueAnimator.ofObject方法是為了可以對Point對象進行操作,更為形象具體。還有就是通過ofObject方法使用了自定義的TypeEvaluator對象,由此得到了fraction值,該值是一個從0-1變化的小數。所以該方法的後兩個參數startValue(new Point()),endValue(new Point())並沒有實際意義,也可以直接不寫,此處寫上主要是為了便於理解。同樣道理也可以直接使用ValueAnimator.ofFloat(0f, 1f)方法獲取到一個從0-1變化的小數。
final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() { @Override public Object evaluate(float fraction, Object startValue, Object endValue) { //參數fraction用於表示動畫的完成度,我們根據它來計算當前的動畫值 double angle = Math.toRadians(90 * fraction); int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle)); int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle)); Point point = new Point(x, y); return point; } }, new Point(), new Point());通過fraction,我們計算得到小球擺動時的角度變化值,0-90度
mSwingRadius-mGlobeRadius表示的值是圖中綠色直線的長度,擺動的路線,小球圓心的路線是一個以(mSwingRadius-mGlobeRadius)為半徑的弧線,變化的X值為(mSwingRadius-mGlobeRadius)*sin(angle),變化的y值為(mSwingRadius-mGlobeRadius)*cos(angle)
對應的小球實際的圓心坐標為(mSwingRadius-x,mGlobeRadius+y)
右邊小球運動路線與左邊類似,僅僅是方向不同。右邊小球實際的圓心坐標(mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + x,mGlobeRadius+y)
可見左右兩邊小球的縱坐標是相同的,僅橫坐標不同。
float fraction = anim.getAnimatedFraction(); //判斷是否是fraction先減小後增大,即是否處於即將向上擺動狀態 //在每次即將向上擺動時切換小球 if (lastSlope && fraction > mLastFraction) { isNext = !isNext; } //記錄上一次fraction是否不斷減小
lastSlope = fraction < mLastFraction; //記錄上一次的fraction mLastFraction = fraction;
那麼如何捕捉到這個時間點呢?
小球拋起時fraction值不斷增大,小球落下時fraction值不斷減小。小球即將拋起的時刻,就是fraction從不斷減小轉變為不斷增大的時刻。代碼中記錄上一次fraction是否在不斷減小,然後比較這一次fraction是否在不斷增大,若兩個條件均成立則切換運動的小球。
anim.setDuration(200); //設置補間器,控制動畫的變化速率 anim.setInterpolator(new DecelerateInterpolator()); anim.start();設置動畫的持續時間為200毫秒,讀者可以通過更改該值而達到修改小球擺動速度的目的。
設置動畫的補間器,由於小球拋起是一個逐漸減速的過程,落下是一個逐漸加速的過程,所以使用DecelerateInterpolator實現減速效果,在倒序播放時為加速效果。
啟動動畫,鐘擺效果的自定義View進度條就實現了!趕快運行,看看效果吧!
如有纰漏,敬請海涵。
支持margin,gravity以及水平,垂直排列最近在學習android的view部分,於是動手實現了一個類似ViewPager的可上下或者左右拖動的ViewGroup
這一篇來記錄一下使用ActiveAndroid關系型數據庫進行本地對象的保存。1.ActiveAndroid的介紹該項目的github地址:https://github.
AutoCompleteTextView是一個可編輯的文本,自動顯示與用戶輸入字符串部分匹配和位置匹配的字符串,從中進行選擇。 介紹幾個方法:public cla
什麼是廣播在Android中,Broadcast是一種廣泛運用的在應用程序之間傳輸信息的機制。我們拿廣播電台來做個比方。我們平常使用收音機收音是這樣的:許許多多不同的廣播