第一次自己寫自定義控件,記錄下一些不懂的知識點。
實現一個帶頭部的ScrollView 下拉頭部放大,上滑有時差效果
下面是上代碼:
public class PullToZoomScrollView extends ScrollView{
private boolean isonce;//加載該View的布局時是否是第一次加載,是第一次就讓其實現OnMeasure里的代碼
private LinearLayout mParentView;//布局的父布局,ScrollView內部只能有一個根ViewGroup,就是此View
private ViewGroup mTopView;//這個是帶背景的上半部分的View,下半部分的View用不到的
private int mScreenHeight;//整個手機屏幕的高度,這是為了初始化該View時設置mTopView用的
private int mTopViewHeight;//這個就是mTopView的高度
private int mCurrentOffset=0;//當前右側滾條頂點的偏移量。ScrollView右側是有滾動條的,當下拉時,
//滾動條向上滑,當向下滑動時,滾動條向下滑動。
private ObjectAnimator oa;//這個是對象動畫,這個在本View里很簡單,也很獨立,就在這里申明一下,后面有兩個方法
//兩個方法是:setT(int t),reset()兩個方法用到,其他都和它無關了。
/**
* 初始化獲取高度值,并記錄
* @param context
* @param attrs
*/
public PullToZoomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOverScrollMode(View.OVER_SCROLL_NEVER);
WindowManager wm= (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics=new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics); //獲取描述該顯示的大小和密度的顯示指標
mScreenHeight=metrics.heightPixels; //用顯示器的高度
mTopViewHeight=mScreenHeight/2-(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, context.getResources().getDisplayMetrics());
}
/**
* 將記錄的值設置到控件上,并只讓控件設置一次
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!isonce) {
mParentView = (LinearLayout) this.getChildAt(0);
mTopView = (ViewGroup) mParentView.getChildAt(0);
mTopViewHeight = mTopView.getLayoutParams().height;
mTopView.getLayoutParams().height = mTopViewHeight;
isonce=true;
}
}
private float startY=0;//向下拉動要放大,手指向下滑時,點擊的第一個點的Y坐標
private boolean isBig;//是否正在向下拉放大上半部分View
private boolean isTouchOne;//是否是一次連續的MOVE,默認為false,
//在MoVe時,如果發現滑動標簽位移量為0,則獲取此時的Y坐標,作為起始坐標,然后置為true,為了在連續的Move中只獲取一次起始坐標
//當Up彈起時,一次觸摸移動完成,將isTouchOne置為false
private float distance=0;//向下滑動到釋放的高度差
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action =ev.getAction();
switch (action){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if(mCurrentOffset<=0){
if(!isTouchOne){
startY=ev.getY();
isTouchOne=true;
}
distance=ev.getY()-startY;
if(distance>0){
isBig=true;
setT((int)-distance/4);
}
}
break;
case MotionEvent.ACTION_UP:
if(isBig) {
reset();
isBig=false;
}
isTouchOne=false;
break;
}
return super.onTouchEvent(ev);
}
/**
* 對象動畫要有的設置方法
* @param t
*/
public void setT(int t) {
scrollTo(0, 0);
if (t < 0) {
mTopView.getLayoutParams().height = mTopViewHeight-t;
mTopView.requestLayout();
}
}
/**
* 主要用于釋放手指后的回彈效果
*/
private void reset() {
if (oa != null && oa.isRunning()) {
return;
}
oa = ObjectAnimator.ofInt(this, "t", (int)-distance / 4, 0);
oa.setDuration(150);
oa.start();
}
/**
* 這個是設置向上滑動時,上半部分View滑動速度讓其小于下半部分
* @param l
* @param t
* @param oldl
* @param oldt
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
mCurrentOffset = t;//右邊滑動標簽相對于頂端的偏移量
//當手勢上滑,則右側滾動條下滑,下滑的高度小于TopView的高度,則讓TopView的上滑速度小于DownView的上滑速度
//DownView的上滑速度是滾動條的速度,也就是滾動的距離是右側滾動條的距離
//則TopView的速度要小,只需要將右側滾動條的偏移量也就是t縮小一定倍數就行了。我這里除以2速度減小1倍
if (t <= mTopViewHeight&&t>=0&&!isBig) {
mTopView.setTranslationY(t / 2);//使得TopView滑動的速度小于滾輪滾動的速度
}
if(isBig){
scrollTo(0,0);
}
}
}
下面記錄流程:
1.創建PullToZoomScrollView 繼承 scrollView
這里只提供了一個構造方法必須在在xml文件中創建。
構造方法中做了幾件事:設置ScrollView模式 this.setOverScrollMode(View.OVER_SCROLL_NEVER); 去除下拉時陰影
計算TopView的高度:獲取WindowManager 從windowManager中拿到 metrics 屏幕數據參數,
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, context.getResources().getDisplayMetrics());
這里解釋下這個函數:將包含維度的解壓縮復合數據值轉換為其最終浮點值。
三個參數分別為:
unit:轉換單位。
value: 應用單位的價值。
metrics:在轉換中使用的當前顯示指標 - 提供顯示密度和縮放信息。
2.onMeasure()中,當第一次執行時,拿到mParentView 、mTopView 、并設置mTopViewHeight.
- onTouchEvent(MotionEvent ev)
MotionEvent.ACTION_MOVE,在頂部且下滑時,記錄下第一次滑動的點startY
通過之后的觸摸點計算出滑動距離distance,并設置mTopView的高度。
這里記錄下 requestLayout 與 invalidate的區別:
requestLayout:當view確定自身已經不再適合現有的區域時,該view本身調用這個方法要求parent view重新調用他的onMeasure onLayout來對重新設置自己位置。
特別的當view的layoutparameter發生改變,并且它的值還沒能應用到view上,這時候適合調用這個方法。
invalidate:該方法的調用會引起View樹的重繪,常用于內部調用(比如 setVisiblity())或者需要刷新界面的時候,需要在主線程(即UI線程)中調用該方法。一般只會調用onDraw重新繪制。
MotionEvent.ACTION_UP,在滑動結束后如果動畫還未結束則調用reset()方法
執行動畫
oa = ObjectAnimator.ofInt(this, "t", (int)-distance / 4, 0);
4.onScrollChanged
在滑動時記錄偏移量 mCurrentOffset
這里產生頭部時差效果 將mTopView的滑動速度小于滾輪滑動的速度
if (t <= mTopViewHeight&&t>=0&&!isBig) {
mTopView.setTranslationY(t / 2);//使得TopView滑動的速度小于滾輪滾動的速度
}
如果下拉放大時,執行scrollTo(0,0).
第一次寫技術文章,寫的不好見諒