Android自定义View流程(卫星菜单例子演示)

序言(扯)

由于现在大公司放出的实习offer大多为暑期,本学期课程不多,所以找了一家公司可以立刻入职的去实习下,相比大公司,小公司多采取硬上的策略,对于解决编程问题能力的提升是相当大的,在拉勾投了一家,前天去面试,然后一个自定义view把我问跪了,讲到复用性,讲了很不优雅的一种实现方式,不过最终学习了下,实现思路和我当时讲的差不多,但这确实是我开发中一个短板,但是其他问题回答的还是蛮不错的,还是给了offer,产品真心赞,团队也超赞,CEO=CMU(CS),听COO讲了下产品,分析的真是让我瞠目结舌。下周入职,搬,搬,搬。

今天来写自定义View了,之前看任主席的书,看了View的绘制和事件响应机制,对view底层有了较深理解,但是说到自定义一个View,下不去手。写个卫星菜单,后续将根据这个项目中出现的问题,然后在后续对自定义view的问题和view底层的一些东西进行讲解下。

自定义控件过程

自定义控件的流程大致分为以下几步

  • 定义属性

  • 定义xml文件

  • 自定义View获取属性

  • onMeasure()

  • onLayout()

自定义view的目的是为了提升我们的视觉体验,所以一般我们会辅助一些动画来提升体验,为了增强交互,我们也要为其增加一些交互,所以我么需要对其中进行一些事件的监听,自定义监听器,然后在我们需要的地方进行回调,考虑完这些,我们需要再考虑的是如何提高这些view的复用性。还有当我们多个空

View的绘制流程

  • OnMeasure

  • OnLayout

  • OnDraw

对于自定义View,我们通常会重写这三个方法,重写那些,取决于我们的自定义View从哪里继承,然后要实现什么样的功能。大致归纳有以下几点。

  • 继承View
    实现一些不规则的图形,需要重写onDraw方法进行绘制

  • 继承ViewGroup
    需要实现对于子控件的测量和布局

  • 继承特定View
    较容易实现

  • 继承特定ViewGroup
    无需处理测量和布局

实现卫星菜单

有了前面的一点小储备,接下里要动手实现我们的小demo了,当然我这个demo的实现思路也是参考网上的思路和实现,首先明确我们要实现的样式是如何的。上一张图
Android自定义View流程(卫星菜单例子演示)_第1张图片
如上图所示,我们需要一个按钮,触发后向发生卫星弹射出5个子View,然后点击之后会缩回,根据上面的自定义View的分类,我们不难发现,我们需要的是通过继承ViewGroup来实现,继承自ViewGroup,那么我们要对其进行一个测量,然后是布局,难点就是在布局上,如何布局呢?这里不难发现,我们可以通过为其设定半径,然后将这个几个卫星平均分布在中心的圆周围。然后对于我们中间的View进行事件的监听,还有对于分布在周围的View的监听。上述即为实现的核心环节。接下来按照我们的思路,贴出代码,供以参考,熟悉整个流程。

  • 属性设置
    这个之前真的没有用到过,又涨姿势了)喜悦脸,之前在使用一些自定义控件中,我们不难发现会有一些特殊的属性,我们可以为其设置值,然后我们使用的自定义View就可以根据这个属性制定的值进行显示,如何设置这些值,然后在View的内部又是如何获得的这些值呢?



    
        
        
        
        
    
    

    
        
        
    

在values下,设置我们的资源属性文件,然后声明我们的自定义View所需要的属性,然后在我们自定义View添加命名空间之后,我们就可以使用这些属性了,这都是很次要的了,然后完成我们的布局。

  • 设置布局



        
            
        
        
        
        
        
        

    

在View中获取这些属性

 TypedArray a = context.getTheme().obtainStyledAttributes(attributeSet, R.styleable.ArcMenu,defStyle,0);
        int pos = a.getInt(R.styleable.ArcMenu_postion, POS_RIGHT_BOTTOM);
        switch (pos)
        {
            case POS_LEFT_BOTTOM:
                mPostion = Position.LEFT_BOTTOM;
                break;
            case POS_LEFT_TOP:
                mPostion = Position.LEFT_TOP;
                break;
            case POS_RIGHT_BOTTOM:
                mPostion = Position.RIGHT_BOTTOM;
                break;
            case POS_RIGHT_TOP:
                mPostion = Position.RIGHT_TOP;
                break;
            default:
                break;

        }
        mRadius = (int)a.getDimension(R.styleable.ArcMenu_radius,(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,100
                ,getResources().getDisplayMetrics()));
  • 测量子View
    因为我们继承的是一个ViewGroup, 所以我们需要进行子View的测量。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        for(int i=0; i
  • View布局

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if(changed){
            layoutCButton();
            int count = getChildCount();
            for(int i=0; i

这里涉及到我们在布局上的数学知识了。因为当我们的卫星菜单处在四个不同的角的时候,我们在坐标的设置上就会出现问题了,首先是将我们的恒星按钮进行布局,然后是卫星按钮围绕其周围进行布局,对于恒星的布局,也需要我们根据四个不同的位置进行选择。

private void layoutCButton(){
        mCButton = getChildAt(0);
        mCButton.setOnClickListener(this);
        int left = 0;
        int top = 0;
        int width = mCButton.getMeasuredWidth();
        int height = mCButton.getMeasuredHeight();
        switch (mPostion){
            case LEFT_BOTTOM:
                left = 0;
                top = getMeasuredHeight()-height;
                break;
            case RIGHT_BOTTOM:
                left = getMeasuredWidth()-width;
                top = getMeasuredHeight()-height;
                break;
            case LEFT_TOP:
                left = 0;
                top = 0;
                break;
            case RIGHT_TOP:
                left = getMeasuredWidth()-width;
                top = 0;
                break;
            default:break;
        }
        mCButton.layout(left, top, left + width, top + height);
    }

这样我们的基本完成了界面的布局了,然后是对于

  • 点击事件的监听和响应

@Override
    public void onClick(View v) {
        rotateButton(v, 0f, 360f, 300);
        toggleMenu(300);
    }

这里为了增强体验感,对于按钮,在点击的时候添加了一个旋转动画,然后是控制卫星的弹射,可能会有点疑问哈,我们之前写代码对于按钮事件的监听,我们不都是要将其和具体的控件绑定吗,但是这里为什么不需要,原因是,在这里只有它可以响应这个事件,其它的都是不可见,在可见的时候,我们又为其设置了监听器,一会演示,因此事件会被子view自己的监听事件拦截掉,而不会在其父view级的事件处理机制中出现。这里涉及到View的事件传递机制,是从父View向子View进行传递,如果事件被消耗了,则父View没有响应,当然可以在父View中设置拦截机制。这里的toggleMenu的实现是响应我们的卫星向周边弹射的核心。

public void toggleMenu(int duration){
        int count = getChildCount();
        for(int i=0; i

说下实现原理吧,因为这里我们使用的不是属性动画,通过的是平移,其特点是当其移动了之后,其实际位置还是在原处,所以是不可以点击的,解决这个问题的方式是将其固定在我们要点击的位置,然后设置为不可见,移动的时候,让其从一个负位置移动到当前这个位置,产生一种发射的特效,同时给其添加了一个旋转特效,让其显得更加的自然圆滑,之后,我们对这些子View设置监听事件,监听事件是让点击的按钮变大,未点击的按钮消失,然后变化这些状态。

private void menuItemAnim(int pos){
        for(int i=0; i

遇到的问题

  • 在对View进行设置可见或者不可见的时候,要清除掉当前的动画

结束语

具体动画代码不再贴出,可以去我的Github上查看具体代码,同时本人想实现一个自定义View的集合篇,将写一些自定义View的东西具体的实现贴一下,然后在Github上挂出代码, 大家有什么推荐的吗?这样我们可以一起学习,共同进步。

你可能感兴趣的:(自定义view,android)