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

尝试着仿造 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
                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 离屏幕右侧边距确实改变了。
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值