自定義控件
1.onMesure
決定內部View(子View)以及自身的寬高
2.onLayout
決定子View放置的位置
3.onTouchEvent
自定義屬性
1.書寫xml文件 (values/attr.xml)
2.在佈局文件中使用,注意xmlns
3. 在構造方法中獲取我們設置的值
本例在前一例子的基礎上添加了自定義屬性以及按鈕點擊切換菜單打開/關閉功能
至於屬性文件的具體設定以及切換按鈕的點擊時間就不貼出來了
這裏只設定了mMenuRightPadding這一個屬性
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
/**
* Created by wjc on 2017/1/11.
* <p/>
* 自定義ViewGroup重點要實現的方法
* 1.onMeasure
* 決定內部View(子View)以及自身的寬和高
* 2.onLayout
* 決定子View的放置的位置
* 3.onTouchEvent
*/
public class SlidingMenu1 extends HorizontalScrollView {
private LinearLayout mWrapper;//包裹菜單和內容
private ViewGroup mMenu;//菜單
private ViewGroup mContent;//內容
private int mScreenWidth;//屏幕寬度
private int mMenuRightPadding;//菜單右側距離屏幕右側的距離
private int mMenuWidth;//menu的寬度
//設置一個標記,防止onMeasure多次測量
private boolean once = true;
//設置標記,菜單當前是否打開
private boolean isOpen;
public SlidingMenu1(Context context) {
this(context,null);
Log.e("hhhahah"," xixixix ");
}
/**
* 未使用自定義屬性時,會調用這個兩個參數的構造函數
*/
public SlidingMenu1(Context context, AttributeSet attrs) {
this(context,attrs,0);
Log.e("hhhahah"," "+context.obtainStyledAttributes(attrs,R.styleable.SlidingMenu1));
}
/**
* 如果在Code中實例化一個View會調用第一個構造函數,如果在xml中定義會調用第二個構造函數,
* 而第三個函數系統是不調用的,要由View(我們自定義的或系統預定義的View,如此處的CustomTextView和Button)顯式調用,
* 比如在這裏我們在第二個構造函數中調用了第三個構造函數,並將R.attr.CustomizeStyle傳給了第三個參數。
*
*當在佈局文件中調用本控件時使用了自定義屬性時,會調用此構造方法(三個參數)
*/
public SlidingMenu1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//獲取自定義屬性,這裏獲取了<declare-styleable name="SlidingMenu1">的所有值
TypedArray ta=context.getTheme().obtainStyledAttributes(attrs,R.styleable.SlidingMenu1,defStyleAttr,0);
/**
* 獲取dimension的方法有幾種,區別不大
* 共同點是都會將dp,sp的單位轉爲px,px單位的保持不變
*
* getDimension() 返回float,
* getDimensionPixelSize 返回int 小數部分四捨五入
* getDimensionPixelOffset 返回int,但是會抹去小數部分
*/
mMenuRightPadding=(int)ta.getDimension(R.styleable.SlidingMenu1_rightPadding,50);
ta.recycle();
WindowManager wm = (WindowManager) context.getSystemService(context.WINDOW_SERVICE);
//DisplayMetrics經過getMetrics()方法後 ,寬高就被賦值了
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth = outMetrics.widthPixels;
}
/**
* 設置子View和自身的寬和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//如果是第一次,進行測量,並且在測量設置完成後,once=false
if (once) {
//取出HorizontalScrollView的第一個元素也就是LineaLayout
mWrapper = (LinearLayout) getChildAt(0);
//LineaLayout的第一個元素menu
mMenu = (ViewGroup) mWrapper.getChildAt(0);
mContent = (ViewGroup) mWrapper.getChildAt(1);
//寬度的設置,menu和content的寬度就可以決定wrapper的寬度,就不用設置了
mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
mContent.getLayoutParams().width = mScreenWidth;
once = false;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 在不做任何偏移的起始情況下,menu是完全顯示的
* 所以要通過設置偏移量,先將menu隱藏
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//參數changed表示view有新的尺寸或位置;
if (changed) {
//整個佈局偏移一個菜單的寬度,使菜單完全隱藏
// 這個偏移是瞬間完成的
this.scrollTo(mMenuWidth, 0);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
/**
* 這裏的ScrollX表示用戶在滑動擡起之後 未拉出的菜單寬度
* 就是仍然隱藏的寬度
* 當這個寬度小於菜單寬度的一半時,應讓菜單顯示
* 多於菜單的一半時,讓菜單繼續隱藏
*/
int scrollX = getScrollX();
if (scrollX >= mMenuWidth / 2) {//完全隱藏
this.smoothScrollTo(mMenuWidth, 0);
isOpen=false;
} else {//完全顯示,就是不做偏移的起始位置
this.smoothScrollTo(0, 0);
isOpen=true;
}
return true;
}
return super.onTouchEvent(ev);
}
//打開菜單
public void openMenu(){
if(isOpen)return;
this.smoothScrollTo(0,0);
isOpen=true;
}
//關閉菜單
public void closeMenu(){
if(!isOpen)return;
this.smoothScrollTo(mMenuWidth,0);
isOpen=false;
}
//菜單狀態切換
public void toggle(){
if(isOpen){
closeMenu();
}else{
openMenu();
}
}
/**
* 添加動畫效果,看起來菜單就好像一直在content背面,拉開content,菜單就顯示出來
* 重點是 l 參數
* l 代表滑動後當前ScrollView可視界面的左上角在整個ScrollView的X軸中的位置
* 這裏l=getScrollX(); 也就是滑動的當前未拉出的部分,
* 我們需要設置translationX 將那部分拉出
*
*
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
//調用屬性動畫 設置TranslationX
ObjectAnimator.ofFloat(mMenu,"translationX",l).setDuration(0).start();
//實現QQ6.6效果的話 大概是這樣
ObjectAnimator.ofFloat(mMenu,"translationX",l*0.7f).setDuration(0).start();
}
}
這裏有幾個點需要注意下1. * 如果在Code中實例化一個View會調用第一個構造函數,如果在xml中定義會調用第二個構造函數, * 而第三個函數系統是不調用的,要由View(我們自定義的或系統預定義的View,如此處的CustomTextView和Button)顯式調用, * 比如在這裏我們在第二個構造函數中調用了第三個構造函數,並將R.attr.CustomizeStyle傳給了第三個參數。 一般來說,自定義View的實現只需要兩個參數的構造函數就可以了2.
從typedArray中獲取自定義的屬性值,且值的format="dimension",有以下幾種方法 /** * 獲取dimension的方法有幾種,區別不大 * 共同點是都會將dp,sp的單位轉爲px,px單位的保持不變 * * getDimension() 返回float, * getDimensionPixelSize 返回int 小數部分四捨五入 * getDimensionPixelOffset 返回int,但是會抹去小數部分 */3.關於scrollTo和smoothScrollToscrollTo是瞬間完成的,smoothScrollTo有個動畫過程。他們的x,y兩個參數,都是指偏移完成之後的座標位置。(0,0)表示正常顯示,即控件的左上角置於父控件的左上角4.關於onScrollChanged這個方法是在當前控件(自定義的SlidingMenu)滑動時被調用,並且會在滑動額過程中每一幀都不斷地調用。他的四個參數,是以屏幕的左上角相對於當前View的左上角來計算的。我們要實現滑動content
菜單貼住屏幕左邊不動的效果,只需要,在menu、content一同滑動的基礎上額外再將菜單往右拉動一部分,讓菜單始終處於完全拉出的狀態
原理如下圖