您的位置:首页 > 移动开发 > Android开发

自定义 view 练习(1):用 HorizontalScrollView 实现自定义侧滑菜单

2016-11-23 10:30 435 查看
尝试着仿造 qq 的侧滑菜单,来练习对 HorizontalScrollView 和自定义 View 的使用。



实现基本布局

因为是对侧滑菜单的练习,QQ 那些复杂的布局就不一一实现了,这里直接用背景图来代替。





如上图,我们要实现的布局包括左侧的 Menu 与右部的 Content。默认情况下,Menu 区域是隐藏的,只显示 Content。当滑动 Content 时,再显示出 Menu。

先实现 Menu 的布局:

<?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"
android:background="@mipmap/img_frame_background">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/left_menu_img1"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_1" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_toEndOf="@id/left_menu_img1"
android:layout_toRightOf="@id/left_menu_img1"
android:text="@string/first_item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/left_menu_img2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_2" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_toEndOf="@id/left_menu_img2"
android:layout_toRightOf="@id/left_menu_img2"
android:text="@string/second_item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/left_menu_img3"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_3" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_toEndOf="@id/left_menu_img3"
android:layout_toRightOf="@id/left_menu_img3"
android:text="@string/third_item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/left_menu_img4"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_4" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_toEndOf="@id/left_menu_img4"
android:layout_toRightOf="@id/left_menu_img4"
android:text="@string/forth_item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView

4000
android:id="@+id/left_menu_img5"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:src="@mipmap/img_5" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_toEndOf="@id/left_menu_img5"
android:layout_toRightOf="@id/left_menu_img5"
android:text="@string/fifth_item"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>




相对应的,要创建一个继承自 HorizontalScrollView 的 Class 来自定义 View ,也就是这里的 SlidingMenu 。

那么 activity_main 代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.shmj.mouzhai.scrollerviewmenu.MainActivity">

<com.shmj.mouzhai.scrollerviewmenu.view.SlidingMenu
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">

<include layout="@layout/left_menu"/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/qq"/>
</LinearLayout>
</com.shmj.mouzhai.scrollerviewmenu.view.SlidingMenu>
</RelativeLayout>


自定义 view

我们自定义的 SlidingMenu 应当重写 onMeasure() 、onLayout()、onTouchEvent() 方法,来让 Activity 启动时的 Menu 隐藏在屏幕之外,随着滑动逐渐显示。

package com.shmj.mouzhai.scrollerviewmenu.view;

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;

/**
* 自定义侧滑菜单
* <p>
* Created by Mouzhai on 2016/11/22.
*/

public class SlidingMenu extends HorizontalScrollView {

private LinearLayout mWrapper;
private ViewGroup mMenu;
private ViewGroup mContent;
private int mScreenWidth;
private int mMenuWidth;

private int mMenuRightPadding = 50;//菜单距离右侧边距,单位 dp

private boolean once = false;

/**
* 未设置自定义属性时,默认调用
*/
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);

WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
mScreenWidth = displayMetrics.widthPixels;

//将 dp 转化为像素值
mMenuRightPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50,
context.getResources().getDisplayMetrics());
}

/**
* 设置自身及子 View 的宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!once) {
mWrapper = (LinearLayout) getChildAt(0);
mMenu = (ViewGroup) mWrapper.getChildAt(0);
mContent = (ViewGroup) mWrapper.getChildAt(1);
mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
mContent.getLayoutParams().width = mScreenWidth;
once = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

/**
* 决定子 view 的位置
* 设置偏移量来隐藏 menu
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
this.scrollTo(mMenuWidth, 0);
}
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
int scrollX = getScrollX();//隐藏在屏幕左侧之外的宽度
//如果隐藏宽度大于一半则隐藏 menu,否则显示
if (scrollX >= mMenuWidth / 2) {
this.smoothScrollTo(mMenuWidth, 0);
} else {
this.smoothScrollTo(0, 0);
}
return true;
}
return super.onTouchEvent(ev);
}
}


这样就基本实现了我们开头的预览效果。

当然为了美观,可以设置 Title 隐藏。在 MainActivity 里设置:

if(getSupportActionBar() != null){
getSupportActionBar().hide();
}


即可。

(这里是继承自 AppCompatActivity 的写法,如果是继承自 Activity, 直接用

requestWindowFeature(Window.FEATURE_NO_TITLE);


也是同样的效果。)

自定义属性

但是这就完了吗?不,通常来说,自定义 view 也应当自定义一些属性,来方便使用。这里自定义 Menu 距离屏幕右侧的距离来作为练习。

首先在 values 目录下新建 attr.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SlidingMenu">
<attr name="rightPadding" format="dimension"/>
</declare-styleable>
</resources>


这里的 rightPadding 就是自定义的属性, format 表示属性类型 。

接着回到 SlidingMenu,重新书写构造方法:

public SlidingMenu(Context context) {
this(context, null);
}

/**
* 未设置自定义属性时,默认调用
*/
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

/**
* 使用了自定义属性时,调用此构造方法
*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

//获取自定义属性
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SlidingMenu, defStyleAttr, 0);
int n = typedArray.getIndexCount();
for(int i = 0; i<n; i++){
int attr = typedArray.getIndex(i);
switch (attr){
case R.styleable.SlidingMenu_rightPadding:
//默认的距离
//将 dp 转化为像素值
int defaultPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
50, context.getResources().getDisplayMetrics());
mMenuRightPadding = typedArray.getDimensionPixelSize(attr, defaultPadding);
break;
}
}
typedArray.recycle();

WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
mScreenWidth = displayMetrics.widthPixels;
}


利用 TypedArray 来获取自定义的属性值,再对获取到的属性值做处理。

究竟有没有效果呢?现在可以去 activity_main 里设置一下属性啦:

先在跟布局引用:

xmlns:mymenu="http://schemas.android.com/apk/res-auto"


mymenu是自定义的名称,可以随意命名。这句表示引入自定义属性文件。

接着设置 SlidingMenu 的属性:

android:layout_width="match_parent"
android:layout_height="match_parent"
mymenu:rightPadding="200dp"


再次运行,可以看到 Menu 离屏幕右侧边距确实改变了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android