自定義ViewGroup可不像自定義View那麼簡單android
1.自定義ViewGroup中花樣佈局子View
2.移動View用layout、translation、TranslationAnimation、ViewPropertyAnimator、scrollTo、scrollBy有什麼區別?
3.ViewGroup裏怎麼給孩子加動畫
4.慣性移動? 也許你能夠了解一下
複製代碼
效果 1 | 效果 2 |
---|---|
這是我曾經測試畫出的一張圖,描述了ViewGroup+兩個孩子的生命函數調用狀況git
在這補充一點,ViewGroup在沒有背景時不會走onDraw方法,但能夠走dispatchDraw
緣由在於View對onDraw的控制時作了限定:[if (!dirtyOpaque) onDraw(canvas)]
你可使用onDraw,在以前設個透明色便可:setBackgroundColor(0x00000000);
複製代碼
貌似一直沒有對Activity與View的生命週期一塊兒作過測試
測試以後發現View加載完成以後(onFinishInflate
)並未當即回調測量、佈局、繪製
而是在onResume
以後View纔會回調onAttachedToWindow-->onMeasure-->onSizeChanged-->onLayout-->onDraw
這一點確實讓我挺驚訝,之前居然沒注意,如今理清了,通暢不少github
2019-02-19 16:50:29.998 : onCreate --------------
2019-02-19 16:50:29.992 : 構造函數: 0
2019-02-19 16:50:29.996 : onFinishInflate: 0
2019-02-19 16:50:33.001 : onStart: ...................
2019-02-19 16:50:33.006 : onResume: ...................
2019-02-19 16:50:33.050 : onAttachedToWindow:
2019-02-19 16:50:33.207 : onMeasure: 0
2019-02-19 16:50:33.243 : onMeasure: 0
2019-02-19 16:50:33.354 : onSizeChanged: 1948
2019-02-19 16:50:33.358 : onLayout: 1948
2019-02-19 16:50:33.395 : onDraw: 1948
複製代碼
排兵佈陣
)經測試發現注意點:編程
[1].必須onMeasure中測量孩子的尺寸,不然沒法顯示
[2].必須onLayout中佈局孩子的位置,不然沒法顯示
[3].在onLayout中孩子不能用view.getHeight()獲取尺寸(由於爲0),只能用view.getMeasuredHeight
複製代碼
這裏使用適配器模式,跟ListView一個套路,實際上是很是簡單,看箭頭所指
這裏暫時不對ViewGroup進行測量,先填滿。對子View用自帶的測量方法measureChildren
canvas
public class FlowerLayout extends ViewGroup {
private int mRadius;
private static final String TAG = "FlowerLayout";
--->private ListAdapter mAdapter;
--->public void setAdapter(ListAdapter adapter) {
mAdapter = adapter;
}
public FlowerLayout(Context context) {
this(context, null);
}
public FlowerLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
setBackgroundColor(0x55D3E8FD);
}
private void init(AttributeSet attrs) {
}
private void formFlower() {
for (int i = 0; i < mAdapter.getCount(); i++) {
---> View petal = mAdapter.getView(i, null, this);
addView(petal);//填入花瓣
}
}
@Override
protected void onAttachedToWindow() {
Log.e(TAG, "onAttachedToWindow: ");
---> if (mAdapter != null) {
formFlower();
}
super.onAttachedToWindow();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//TODO 佈局子view
}
}
---->[Activity中使用]----------------
setContentView(R.layout.activity_flower);
FlowerLayout flowerLayout = findViewById(R.id.id_fl);
ArrayList<Petal> petals = new ArrayList<>();
petals.add(new Petal(R.mipmap.icon_1, "icon_1"));
petals.add(new Petal(R.mipmap.icon_2, "icon_2"));
petals.add(new Petal(R.mipmap.icon_3, "icon_3"));
petals.add(new Petal(R.mipmap.icon_4, "icon_4"));
petals.add(new Petal(R.mipmap.icon_5, "icon_5"));
petals.add(new Petal(R.mipmap.icon_6, "icon_6"));
petals.add(new Petal(R.mipmap.icon_7, "icon_7"));
petals.add(new Petal(R.mipmap.icon_8, "icon_8"));
petals.add(new Petal(R.mipmap.icon_9, "icon_9"));
petals.add(new Petal(R.mipmap.icon_10, "icon_10"));
flowerLayout.setAdapter(new FlowerAdapter(petals));
---->[FlowerAdapter視圖適配器]---------------------------
public class FlowerAdapter extends BaseAdapter {
private List<Petal> mPetals;
public FlowerAdapter(List<Petal> petals) {
mPetals = petals;
}
@Override
public int getCount() {
return mPetals.size();
}
@Override
public Object getItem(int position) {
return mPetals.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.flower_item, parent, false);
ImageView iv = view.findViewById(R.id.id_pic);
iv.setImageResource(mPetals.get(position).resId);
TextView tv = view.findViewById(R.id.id_info);
tv.setText(mPetals.get(position).info);
return view;
}
}
複製代碼
這裏關鍵在排布這裏給張圖先:子View佈局的左上右下數組
---->[FlowerLayout#onLayout]----------------
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
int childW = childView.getMeasuredWidth();
int childH = childView.getMeasuredHeight();
int topPos = (int) (childH * i*0.5f);
int leftPos = 0;
childView.layout(leftPos, topPos, leftPos + childW, topPos + childH);
}
|-- 如今只要修改topPos和leftPos就能夠改變子View佈局
複製代碼
---->[FlowerLayout#onLayout]----------------
int count = mAdapter.getCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
int childW = childView.getMeasuredWidth();
int childH = childView.getMeasuredHeight();
mRadius = (getWidth()-childW) / 2;
float posX = childW / 2 + mRadius - mRadius * cos(i * 360.f / count);
float posY = childH / 2 + mRadius - mRadius * sin(i * 360.f / count);
int leftPos = (int) (posX - childW / 2);
int topPos = (int) (posY - childH / 2);
childView.layout(leftPos, topPos, leftPos + childW, topPos + childH);
}
private float cos(float θ) {
return (float) Math.cos(θ / 180 * Math.PI);
}
private float sin(float θ) {
return (float) Math.sin(θ / 180 * Math.PI);
}
複製代碼
這就比較容易了,一個監聽搞定bash
//在造成View以後就添加點擊事件
private void formFlower() {
for (int i = 0; i < mAdapter.getCount(); i++) {
View petal = mAdapter.getView(i, null, this);
int position = i;
if (mOnItemClickListener != null) {
petal.setOnClickListener(v -> {
ObjectAnimator.ofFloat(v, "ScaleX", 1f, 0.8f,1f).setDuration(200).start();
ObjectAnimator.ofFloat(v, "ScaleY", 1f, 0.8f,1f).setDuration(200).start();
mOnItemClickListener.onClick(v, this, position);
});
}
addView(petal);//填入花瓣
}
//----------------------------條目點擊監聽-------------------
public interface OnItemClickListener {
void onClick(View v, ViewGroup viewGroup, int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
複製代碼
這忽然讓我想到一個好玩的東西,那就是點陣控位。
點陣控位可使用二維數組,也可使用字符串,也可使用像素點。
具體的能夠詳見個人這篇:這裏就放一張核心的分析圖:咱們這裏不畫圓,而是取點位微信
/**
* 用來顯示點陣的二維數組
*/
public static final int[][] digit_test = new int[][]
{
{0, 0, 0, 1, 0, 0, 0},
{0, 0, 1, 0, 1, 0, 0},
{0, 0, 1, 0, 1, 0, 0},
{0, 1, 0, 1, 0, 1, 0},
{0, 1, 0, 0, 0, 1, 0},
{0, 1, 0, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0},
};
/**
* 點位解析器
* @param w 單體寬
* @param h 單體高
* @return 解析成的點位數組
*/
private List<Point> renderDigit(int w, int h) {
List<Point> points = new ArrayList<>();
for (int i = 0; i < digit_test.length; i++) {
for (int j = 0; j < digit_test[j].length; j++) {//一行一行遍歷,遇到1就畫
if (digit_test[i][j] == 1) {
int rX = (j * 2 + 1) * (w + 1);//第(i,j)個點圓心橫座標
int rY = (i * 2 + 1) * (h + 1);//第(i,j)個點圓心縱座標
points.add(new Point(rX, rY));
}
}
}
return points;
}
---->[onLayout使用點位]-------------------
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
List<Point> points = renderDigit(
getChildAt(0).getMeasuredWidth() / 2,
getChildAt(0).getMeasuredHeight() / 2
);
int count = mAdapter.getCount();
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
int childW = childView.getMeasuredWidth();
int childH = childView.getMeasuredHeight();
mRadius = (getWidth() - childW) / 2;
int leftPos = (int) (points.get(i).x - childW / 2);
int topPos = (int) (points.get(i).y - childH / 2);
childView.layout(leftPos, topPos, leftPos + childW, topPos + childH);
}
}
複製代碼
ok了,只要把1放在你想要的位置,子View就在那裏,
不過簡單一點的還好說,要是愛心...來看神技:ide
用黑白(就至關於上面1,0)來標識點位,再根據Bitmap的像素進行
Bitmap內存殺手? 7*7像素的Bitmap也就九牛一毛...
就是下面的小不點,你能夠下載玩玩。有PS,你也能夠用ps本身戳點函數
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.heart);
/**
* 點位解析器
*
* @param bitmap bitmap
* @param w 單體寬
* @param h 單體高
* @return 解析成的點位數組
*/
public static List<Point> renderBitmap(Bitmap bitmap, int w, int h) {
List<Point> points = new ArrayList<>();
for (int i = 0; i < bitmap.getWidth(); i++) {
for (int j = 0; j < bitmap.getHeight(); j++) {
int pixel = bitmap.getPixel(i, j);
if (pixel != -1) {//此處過濾掉白顏色
int rX = (i * 2 + 1) * (w + 1);//第(i,j)個點圓心橫座標
int rY = (j * 2 + 1) * (h + 1);//第(i,j)個點圓心縱座標
points.add(new Point(rX, rY));
}
}
}
return points;
}
複製代碼
到這裏排兵佈陣就結束了,相信對onLayout已經能玩的6了吧,接下來上陣殺敵。
既然是測試,就一切從簡,直切問題自己,這裏新建了一個Activity
而且打開了手機自帶的佈局便界顯示,這樣更能說明問題所在
自定義:TestViewGroup+TestView純原生,不加防腐劑
爲了說明問題,這裏的TestViewGroup加了邊距20dp
<?xml version="1.0" encoding="utf-8"?>
<com.toly1994.analyzer.widget.TestViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_width="300dp"
android:layout_height="200dp"
android:background="#5597FFFA">
<com.toly1994.analyzer.widget.TestView
android:id="@+id/view"
android:layout_width="200dp"
android:layout_height="50dp"
android:background="#23F627"/>
</com.toly1994.analyzer.widget.TestViewGroup>
複製代碼
/**
* 做者:張風捷特烈<br/>
* 時間:2019/2/20/020:10:30<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:測試ViewGroup
*/
public class TestViewGroup extends ViewGroup {
public TestViewGroup(Context context) {
super(context);
}
public TestViewGroup(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View child = getChildAt(0);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
/**
* 做者:張風捷特烈<br/>
* 時間:2019/2/20/020:10:30<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:測試View
*/
public class TestView extends View {
public TestView(Context context) {
super(context);
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
複製代碼
這樣對比應該很是明顯:layout真的把佈局移動了,translation只是離家出走而已
layout----- | translation |
---|---|
點擊事件在綠色上 | 點擊事件在綠色上 |
---->[TestViewGroup#onTouchEvent]-------------
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
// useLayout(mChild, x, y);
useTranslation(mChild, x, y);
}
return super.onTouchEvent(event);
}
private void useLayout(View view, int x, int y) {
view.layout(x, y,
x + view.getMeasuredWidth(), y + view.getMeasuredHeight());
//如下四行等價上一行
// mChild.setLeft(x);
// mChild.setTop(y);
// mChild.setRight(x + mChild.getMeasuredWidth());
// mChild.setBottom(y + mChild.getMeasuredHeight());
}
private void useTranslation(View view, int x, int y) {
view.setTranslationX(x);
view.setTranslationY(y);
}
複製代碼
移動動畫都是從家裏開始,屬性動畫移動從當前位置,可是家還在那裏!
也就是傳說中的屬性動畫也並沒有法改變View的佈局位置
TranslationAnimation | ViewPropertyAnimator |
---|---|
點擊事件在家裏 | 點擊事件在綠色上 |
private void useTranslationAnimation(View view, int x, int y) {
TranslateAnimation translateAnimation = new TranslateAnimation(0, x, 0, y);
translateAnimation.setDuration(500);
translateAnimation.setFillAfter(true);
view.startAnimation(translateAnimation);
}
private void useViewPropertyAnimator(View view, int x, int y) {
view.animate().translationX(x).translationY(y).setDuration(500).start();
//下兩句效果同上
// ObjectAnimator.ofFloat(view, "translationX", x).setDuration(500).start();
// ObjectAnimator.ofFloat(view, "translationY", y).setDuration(500).start();
}
複製代碼
很簡單:ValueAnimator唄,在刷新時對layout進行更新
因爲有四個setXXX方法,這裏,簡單一點,使用ObjectAnimator
private void useLayoutAnimate(View view, int x, int y) {
//下兩句效果同上
ObjectAnimator.ofInt(view, "Left", x).setDuration(500).start();
ObjectAnimator.ofInt(view, "Top", y).setDuration(500).start();
ObjectAnimator.ofInt(view, "Right", x+view.getMeasuredWidth()).setDuration(500).start();
ObjectAnimator.ofInt(view, "Bottom", y + view.getMeasuredHeight()).setDuration(500).start();
}
複製代碼
能夠說這兩個方法和上面的不是一輩的人,應用場景有很大區別
這兩個方法是移動一個View內部的全部
子View,調用方並不是
子View
至於To和By的區別,也是老生常談,看圖體悟吧...
scrollTo | scrollBy |
---|---|
---->[onTouchEvent]---------------
useScrollTo(-x, -y);
useScrollBy(-x, -y);
--------------------------------------------
private void useScrollTo(int x, int y) {
scrollTo(x, y);
}
private void useScrollBy(int x, int y) {
scrollBy(x, y);
}
複製代碼
Ok ,基礎知識就到這裏,言歸正傳:
下面這幅圖應該不難吧,若是作不出來...下面的就當看風景吧...
靜態 | 動態 |
---|---|
/**
* @param start 第一個排成圓的View索引
* @param dθ 旋轉角度
*/
private void layoutCircle(int start, float dθ) {
int count = getChildCount();
for (int i = start; i < count; i++) {
View childView = getChildAt(i);
int childW = childView.getMeasuredWidth();
int childH = childView.getMeasuredHeight();
int r = (getWidth() - childW) / 2;
float posX = childW / 2 + r - r * cos(i * 360.f / (count - 1) + dθ);
float posY = childH / 2 + r - r * sin(i * 360.f / (count - 1) + dθ);
int leftPos = (int) (posX - childW / 2);
int topPos = (int) (posY - childH / 2);
childView.layout(leftPos, topPos, leftPos + childW, topPos + childH);
}
}
複製代碼
在點擊的時候觸發mAnimator.start()便可
mAnimator = ValueAnimator.ofInt(0, 360);
mAnimator.setDuration(3000);
mAnimator.addUpdateListener(a -> {
int deg = (int) a.getAnimatedValue();
layoutCircle(1, deg);
});
複製代碼
這裏實現和中心的交換,而且加入移動動畫
無動畫 | 有動畫 |
---|---|
---->[維護成員變量]-------------
private int centerId = 0;//默認中心點
/**
* 交換兩個View的位置
* @param positionMe 點擊者
* @param positionHe 目標
*/
private void swap(int positionMe, int positionHe) {
View me = getChildAt(positionMe);
View he = getChildAt(positionHe);
int TempMeLeft = me.getLeft();
int TempMeTop = me.getTop();
int TempMeRight = me.getRight();
int TempMeBottom = me.getBottom();
me.layout(he.getLeft(), he.getTop(), he.getRight(), he.getBottom());
he.layout(TempMeLeft, TempMeTop, TempMeRight, TempMeBottom);
centerId = positionMe;
}
|--而後只須要在須要的時候觸發便可:
swap(position, centerId);
複製代碼
動畫,剛纔貌似寫過了,直接拿來用
/**
* 交換兩個View的位置
* @param positionMe 點擊者
* @param positionHe 目標
*/
private void swapWithAnim(int positionMe, int positionHe) {
View me = getChildAt(positionMe);
View he = getChildAt(positionHe);
int TempMeLeft = me.getLeft();
int TempMeTop = me.getTop();
useLayoutAnimate(me, he.getLeft(), he.getTop());
useLayoutAnimate(he, TempMeLeft,TempMeTop);
centerId = positionMe;
}
private void useLayoutAnimate(View view, int x, int y) {
ObjectAnimator.ofInt(view, "Left", x).setDuration(500).start();
ObjectAnimator.ofInt(view, "Top", y).setDuration(500).start();
ObjectAnimator.ofInt(view, "Right", x + view.getMeasuredWidth()).setDuration(500).start();
ObjectAnimator.ofInt(view, "Bottom", y + view.getMeasuredHeight()).setDuration(500).start();
}
複製代碼
既然能夠動畫,那麼則麼玩均可以,好比旋轉和放大
動畫就不展開了,詳情可見:Android 動畫 Animator 家族使用指南
旋轉 | 放大 |
---|---|
VelocityTracker
這個類估計聽過的人很少,翻譯出來是
速度追蹤器
,做爲一個好用的類,在此拎出來說一講
它的做用是獲取你滑動的x,y的速度x 左負,y上負
---->[FlowerLayout#init]---------------
private void init(AttributeSet attrs) {
...
velocityTracker = VelocityTracker.obtain();//1.VelocityTracker的建立
}
---->[FlowerLayout#onTouchEvent]---------------
@Override
public boolean onTouchEvent(MotionEvent event) {
View centerView = getChildAt(0);
velocityTracker.addMovement(event);//2.VelocityTracker與event結合
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_MOVE:
velocityTracker.computeCurrentVelocity(1000);//3.計算速度
//4.獲取值
Log.e(TAG, "X velocity: " + velocityTracker.getXVelocity()+
"--Y velocity: " + velocityTracker.getYVelocity());
break;
case MotionEvent.ACTION_UP:
...
break;
}
return true;
}
|--注意第5點:在適當的地方取消和回收
velocityTracker.clear();//取消
velocityTracker.recycle();//回收
|---咱們比較在乎的是計算速度的方法,1000是搞嘛的?
/**
* Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
* velocity of Float.MAX_VALUE.
* 也就是說這裏的第三參是Float的最大值,表示這個速度足以超光速
* @see #computeCurrentVelocity(int, float)
*/
public void computeCurrentVelocity(int units) {
nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
}
/**
* @param units The units you would like the velocity in. A value of 1
* provides pixels per millisecond, 1000 provides pixels per second, etc.
你想要的單位是速度。值1表示像素/毫秒,1000表示像素/秒,等等。
* @param maxVelocity The maximum velocity that can be computed by this method.
* This value must be declared in the same unit as the units parameter. This value
* must be positive. 該方法能夠計算的最大值
*/
public void computeCurrentVelocity(int units, float maxVelocity) {
nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
}
|-- native方法就不挖了
複製代碼
注
接下來的這部分源於陳小緣的Android實現圓弧滑動效果之ArcSlidingHelper篇
我認真研究了一下,並融入了本ViewGroup,他封裝的很是好,我拆了一下截取了和慣性相關的部分
不懂的能夠去深度一下,我就不賣弄脣舌了,GitHub在:ArcSlidingHelper
---->[FlowerLayout#onLayout]--------------------
private void initRotate() {
int width = getWidth();
int height = getHeight();
mPivotX = width/2;
mPivotY = height/2;
mVelocityTracker = VelocityTracker.obtain();
mScrollAvailabilityRatio = .3F;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
View centerView = getChildAt(0);
float x, y;
x = event.getRawX();
y = event.getRawY();
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mAnimator.start();
abortAnimation();
centerView.layout(x, y,
x + centerView.getMeasuredWidth(), y + centerView.getMeasuredHeight());
Log.e("EVENT", "onTouchEvent: " + x + "------" + y);
break;
case MotionEvent.ACTION_MOVE:
handleActionMove(x, y);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
mVelocityTracker.computeCurrentVelocity(1000);
mScroller.fling(0, 0,
(int) mVelocityTracker.getXVelocity(),
(int) mVelocityTracker.getYVelocity(),
Integer.MIN_VALUE, Integer.MAX_VALUE,
Integer.MIN_VALUE, Integer.MAX_VALUE);
startFling();
break;
}
mStartX = x;
mStartY = y;
return true;
}
//------------------------慣性旋轉----------------------------
private Scroller mScroller = new Scroller(getContext());
private int mPivotX, mPivotY;
private float mStartX, mStartY;
private float mLastScrollOffset;
private float mScrollAvailabilityRatio;
private boolean isClockwiseScrolling;
private boolean isShouldBeGetY;
private boolean isRecycled;
private VelocityTracker mVelocityTracker;
private Handler mHandler = new Handler(msg -> {
computeInertialSliding();
return false;
});
/**
* 處理慣性滾動
*/
private void computeInertialSliding() {
checkIsRecycled();
if (mScroller.computeScrollOffset()) {
float y = ((isShouldBeGetY ? mScroller.getCurrY() : mScroller.getCurrX()) * mScrollAvailabilityRatio);
if (mLastScrollOffset != 0) {
float offset = fixAngle(Math.abs(y - mLastScrollOffset));
float deg = isClockwiseScrolling ? offset : -offset;
setRotation(getRotation() + deg);
}
mLastScrollOffset = y;
startFling();
} else if (mScroller.isFinished()) {
mLastScrollOffset = 0;
}
}
/**
* 計算滑動的角度
*/
private void handleActionMove(float x, float y) {
float l, t, r, b;
if (mStartX > x) {
r = mStartX;
l = x;
} else {
r = x;
l = mStartX;
}
if (mStartY > y) {
b = mStartY;
t = y;
} else {
b = y;
t = mStartY;
}
float pA1 = Math.abs(mStartX - mPivotX);
float pA2 = Math.abs(mStartY - mPivotY);
float pB1 = Math.abs(x - mPivotX);
float pB2 = Math.abs(y - mPivotY);
float hypotenuse = (float) Math.sqrt(Math.pow(r - l, 2) + Math.pow(b - t, 2));
float lineA = (float) Math.sqrt(Math.pow(pA1, 2) + Math.pow(pA2, 2));
float lineB = (float) Math.sqrt(Math.pow(pB1, 2) + Math.pow(pB2, 2));
if (hypotenuse > 0 && lineA > 0 && lineB > 0) {
float angle = fixAngle((float) Math.toDegrees(Math.acos((Math.pow(lineA, 2) + Math.pow(lineB, 2) - Math.pow(hypotenuse, 2)) / (2 * lineA * lineB))));
float deg = (isClockwiseScrolling = isClockwise(x, y)) ? angle : -angle;
setRotation(getRotation() + deg);
}
}
/**
* 打斷動畫
*/
public void abortAnimation() {
checkIsRecycled();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
}
/**
* 釋放資源
*/
public void release() {
checkIsRecycled();
mScroller = null;
mVelocityTracker.recycle();
mVelocityTracker = null;
isRecycled = true;
}
/**
* 檢測手指是否順時針滑動
*
* @param x 當前手指的x座標
* @param y 當前手指的y座標
* @return 是否順時針
*/
private boolean isClockwise(float x, float y) {
return (isShouldBeGetY = Math.abs(y - mStartY) > Math.abs(x - mStartX)) ?
x < mPivotX != y > mStartY : y < mPivotY == x > mStartX;
}
/**
* 開始慣性滾動
*/
private void startFling() {
mHandler.sendEmptyMessage(0);
}
/**
* 調整角度,使其在360之間
*
* @param rotation 當前角度
* @return 調整後的角度
*/
private float fixAngle(float rotation) {
float angle = 360F;
if (rotation < 0) {
rotation += angle;
}
if (rotation > angle) {
rotation = rotation % angle;
}
return rotation;
}
/**
* 檢查資源釋放已經釋放
*/
private void checkIsRecycled() {
if (isRecycled) {
throw new IllegalStateException(" is recycled!");
}
}
複製代碼
OK,今天就到這裏
項目源碼 | 日期 | 附錄 |
---|---|---|
V0.1--github | 2018-2-20 | 無 |
發佈名:
View篇:玩一下自定義ViewGroup
捷文連接:juejin.im/post/5c6d19…
筆名 | 微信 | |
---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 |
個人github:github.com/toly1994328
個人簡書:www.jianshu.com/u/e4e52c116…
個人掘金:juejin.im/user/5b42c0…
我的網站:www.toly1994.com
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持