當今的android應用設計中,一種主流的設計方式就是會擁有一個側滑菜單, 以圖為證:
實作這樣的側滑效果,在5.0以前我們用的最多的就是SlidingMenu這個開源架構,而5.0之後,google推出了自己的側滑實作庫,那就是DrawerLayout,它的用法比SlidingMenu更簡單,而且因為是google的親生兒子,是以現在人們更傾向于使用DrawerLayout,但是再怎麼說,這些都是别人實作好的東西,我們隻是拿來用用而已,對于内部的原理,很多程式員卻不怎麼明白,在接下來的文章中我會通過android中的一些基礎控件來實作于此相似的效果,當然,也許還有很多種實作方式,但是基本的原理是類似的。
首先,我們會用到一個控件:HorizontalScrollView 從名字我們就可以了解到,這是一種水準滑動的控件,也就是當内容大于螢幕的寬度的時候,可以左右滑動來使超出螢幕的内容顯示在螢幕上。
第一步:把菜單的布局簡單的寫出來
<span style="font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_centerInParent="true"
>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/img1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item1"
android:layout_toRightOf="@id/img1"
android:layout_centerVertical="true"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/img2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item1"
android:layout_toRightOf="@id/img2"
android:layout_centerVertical="true"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/img3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
<TextView
android:id="@+id/text3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item1"
android:layout_toRightOf="@id/img3"
android:layout_centerVertical="true"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/img4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
<TextView
android:id="@+id/text4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item1"
android:layout_toRightOf="@id/img4"
android:layout_centerVertical="true"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/img5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
<TextView
android:id="@+id/text5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item1"
android:layout_toRightOf="@id/img5"
android:layout_centerVertical="true"
/>
</RelativeLayout>
</LinearLayout>
</RelativeLayout></span></span>
第二步:寫出整體布局
<span style="font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<googleplay.xiaokai.com.qq.SlidMenu
android:id="@+id/horscrview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/img_frame_background"
android:scrollbars="none"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<include layout="@layout/left_menulayout"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qq"
>
</LinearLayout>
</LinearLayout>
</googleplay.xiaokai.com.qq.SlidMenu>
</LinearLayout></span>
</span>
注意:此時的googplay.xiaokai.com.qq.SlidMenu就是我們要實作的控件。
第三步:繼承HorizontalScrollView實作自定義控件
<span style="font-size:18px;">package googleplay.xiaokai.com.qq;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
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 孫曉凱 on 2016/3/27.
*/
public class SlidMenu extends HorizontalScrollView {
int mScreenWit;//螢幕寬度
int mRightWithScr;
LinearLayout mWrap;
ViewGroup mMenu;
ViewGroup mContent;
int mMenuWidth ;
private boolean flag;
public SlidMenu(Context context, AttributeSet attrs) {
super(context, attrs);
//得到螢幕的寬度
WindowManager winmana = (WindowManager) context.getSystemService(context.WINDOW_SERVICE);
DisplayMetrics metris = new DisplayMetrics();
winmana.getDefaultDisplay().getMetrics(metris);
mScreenWit = metris.widthPixels;//得到的是像素
//把50dp轉換成像素
mRightWithScr = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics());
}
public SlidMenu(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!flag) {
mWrap = (LinearLayout) getChildAt(0);//得到此空間中的第一個子控件
mMenu = (ViewGroup) mWrap.getChildAt(0);//得到menu
mContent = (ViewGroup) mWrap.getChildAt(1);//得到内容控件
mMenuWidth = mMenu.getLayoutParams().width = mScreenWit - mRightWithScr;//側滑菜單的寬度為螢幕寬度減去50dp
mContent.getLayoutParams().width = mScreenWit;//設定内容控件寬度
flag = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/*
實作的功能是将menu隐藏,通過設定偏移量
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(changed) {
this.scrollTo(mMenuWidth, 0);//向左移動
}
super.onLayout(changed, l, t, r, b);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action){
case MotionEvent.ACTION_UP:
int scx = getScrollX(); //就是目前view的左上角相對于母視圖的左上角的X軸偏移量
if(scx>=mMenuWidth/2){
this.smoothScrollTo(mMenuWidth,0);
}else{
this.smoothScrollTo(0,0);
}
return true;
}
return super.onTouchEvent(ev);
}
}</span>
此時程式還不夠靈活,比如如果想讓讓菜單距離螢幕右邊的距離是可以自己調控的,應該怎麼辦呢? 此時我們可以自定義一個屬性。
自定義屬性第一步: 在values檔案夾中建立一個attr.xml檔案;
第二步:在檔案中定義屬性
<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SlidMenu">
<attr name="RithtPadding" format="dimension">
</attr>
</declare-styleable>
</resources>
RithtPadding就是自定義的屬性的名稱;
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:my="http://schemas.android.com/apk/res-auto" <!--注意,要使用自己的命名空間--!>
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<googleplay.xiaokai.com.qq.SlidMenu
android:id="@+id/horscrview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/img_frame_background"
android:scrollbars="none"
my:RithtPadding="100dp" <!--自定義的控件--!>
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<include layout="@layout/left_menulayout"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/qq"
>
</LinearLayout>
</LinearLayout>
</googleplay.xiaokai.com.qq.SlidMenu>
</LinearLayout></span>
第三步:在代碼中得到布局檔案中的屬性的值,并進行相應的操作
<span style="font-size:18px;">public SlidMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//得到螢幕的寬度
WindowManager winmana = (WindowManager) context.getSystemService(context.WINDOW_SERVICE);
DisplayMetrics metris = new DisplayMetrics();
winmana.getDefaultDisplay().getMetrics(metris);
mScreenWit = metris.widthPixels;//得到的是像素
<span style="color:#3366ff;">TypedArray array = context.getTheme().obtainStyledAttributes(attrs,R.styleable.SlidMenu,defStyleAttr,0);
int n = array.getIndexCount();
for(int i=0;i<n;i++){
int attr = array.getIndex(i);
switch (attr){
case R.styleable.SlidMenu_RithtPadding:
mRightWithScr = array.getDimensionPixelSize(attr,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()));
break;
}
}
array.recycle();//</span>
}</span>
嗯,這次好像比較完美了,诶?不對,人家的側滑都是在左上角有一個點選按鈕的,一點,菜單就可以出來,再一點,菜單就會進去,好吧,我們來實作它! 隻需要兩步即可:
第一步:在自定義控件中添加以下三個方法:
<span style="font-size:18px;">/*
打開菜單
*/
public void openMenu(){
if(isOpen)return;
else {
this.smoothScrollTo(0,0);//打開
isOpen = true;
}
}
/*
關閉菜單
*/
public void closeMenu(){
if(!isOpen){
return ;
}else{
this.smoothScrollTo(mMenuWidth,0);
isOpen = false;
}
}
/*
切換菜單
*/
public void toggle(){
if(isOpen){
closeMenu();
}else{
openMenu();
}
}</span>
第二步:在布局檔案中定義一個按鈕(這個都會,我就不貼代碼了),然後在使用控件的時候在點選方法中直接調用即可
<span style="font-size:18px;">public class MainActivity extends AppCompatActivity {
private SlidMenu slidmenu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);<span style="font-family: Arial, Helvetica, sans-serif;">//</span><span style="font-family: Arial, Helvetica, sans-serif;">如果繼承的是ActionBarActivity或者是AppCompatActivity就會報錯,</span><span style="font-family: Arial, Helvetica, sans-serif;">如果你執意要用這個方法,請繼承Activity。</span>
// 如果你繼承的是AppCompatActivity或ActionBarActivity請調用下面的方法代替上面的方法
// supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
slidmenu = (SlidMenu) findViewById(R.id.horscrview);
}
public void toggle(View view){
slidmenu.toggle();
}
}
</span>
OK,大功告成,這次總可以了吧! 嗯,看似還行,但是我們還可以做成更絢麗的效果!
實作這種效果也很簡單,主要通過屬性動畫來實作,在自定義控件中添加如下代碼:
<span style="font-size:18px;">/**
* 滾動發生時
*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
super.onScrollChanged(l, t, oldl, oldt);
/**
* 差別1:内容區域1.0~0.7 縮放的效果 scale : 1.0~0.0 0.7 + 0.3 * scale
*
* 差別2:菜單的偏移量需要修改
*
* 差別3:菜單的顯示時有縮放以及透明度變化 縮放:0.7 ~1.0 1.0 - scale * 0.3 透明度 0.6 ~ 1.0
* 0.6+ 0.4 * (1- scale) ;
*
*/
float rightScale = 0.7f + 0.3f * scale;
float leftScale = 1.0f - scale * 0.3f;
float leftAlpha = 0.6f + 0.4f * (1 - scale);
// 調用屬性動畫,設定TranslationX
ViewHelper.setTranslationX(mMenu, mMenuWidth * scale * 0.8f);
ViewHelper.setScaleX(mMenu, leftScale);
ViewHelper.setScaleY(mMenu, leftScale);
ViewHelper.setAlpha(mMenu, leftAlpha);
// 設定content的縮放的中心點
ViewHelper.setPivotX(mContent, 0);
ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);
ViewHelper.setScaleX(mContent, rightScale);
ViewHelper.setScaleY(mContent, rightScale);
}</span>
嗯,這次才是大功告成!
圖:
想要源碼的同學,這是源碼位址:https://github.com/anxiaokai/seslidmenu.git
參考資料:慕課網