Android 控件架构简介&ListView第一部分

3 Android 控件架构和自定义控件详解

3.1 Android 控件架构

  • 控件分类
    • View
    • ViewGroup
  • ViewGroup 和 View 的关系
    • ViewGroup 可以包含多个view,并且管理包含的view控件,通过ViewGroup,整个界面形成了一个树形结构(空间树)上层控件负责下层子控件的测量与绘制,并传递交互事件
    • 通常在activity中调用findViewById()来对控件树进行深度优先遍历来查找对应的元素
    • 在每一棵控件树的顶部都存在一个ViewParent对象,所有的交互事件都由它统一调度和分配
  • View 的测量

    • 测量的三种模式
      • EXACTLY 这种测量模式是在指定了控件大小之后启用的
      • AT_MOST 这种测量模式实在指定控件宽或高为wrap_content 的时候启用的
      • UNSPECIFIED 这种状态没有具体的使用过不清楚
    • View类默认只支持EXACTLY模式,如果想要view支持AT_MOST,那么就要重写onMeasure()方法进行测量

       @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          //在onMeasure的父方法中调用的就是setMeasuredDimension(measureWidth,measureHeight);所以在这里直接调用
          setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));  
      }
      
      /**
       * 测量宽  widthMeasureSpec是一个32位的值 其中高2位为测量模式,低30位为测量的大小,分别通过MeasureSpec.getMode()
       * & MeasureSpec.getSize()  分别来获取
       * @param widthMeasureSpec
       * @return
       */
      private int measureWidth(int widthMeasureSpec){
          //从measureSpec中获取mode & value
          int mode = MeasureSpec.getMode(widthMeasureSpec);
          int size = MeasureSpec.getSize(widthMeasureSpec);
          //判断测量模式
          if(mode == MeasureSpec.EXACTLY){  //这个模式有多大就是多大
              return size;
      
          }else if(mode == MeasureSpec.AT_MOST){ //这个就是为控件添加了wrap_content值 那么就要和父控件的大小进行比较了
              int parentSize = 100;
              return size > parentSize ? parentSize : size;
      
          }
      
          return size;
      }
      
      /**
       * 测量高  同样可以通过测量width的方式来进行
       * @param heightMeasureSpec
       * @return
       */
      private int measureHeight(int heightMeasureSpec){
          //1. 获取测量 mode & size
          int mode = MeasureSpec.getMode(heightMeasureSpec);
          int size = MeasureSpec.getSize(heightMeasureSpec);
      
          //2. 具体判断测量的模式
          if(mode == MeasureSpec.EXACTLY){  //这个模式有多大就是多大
              return size;
      
          }else if(mode == MeasureSpec.AT_MOST){ //这个就是为控件添加了wrap_content值 那么就要和父控件的大小进行比较了
              int parentSize = 100;
              return size > parentSize ? parentSize : size;
      
          }
      
          return size;
      }
    • layout从测量到显示的步骤

      • measure –> layout –> draw –> display
  • View的绘制
    • 使用的工具Canvas Paint两个类
    • 通过重写onDraw()方法来进行绘图
    • 这里的绘制步骤通过后面的2D绘图给出
  • ViewGroup 的测量

    • 通过ViewGroup的特点来看,其下的所有的view都由其管理,那么什么是决定其大小的因素呢,当然是子控件的宽和高,通过遍历所有的子控件的宽高,然后来决定自身的宽高(AT_MOST模式下),其他模式直接指定大小,所有的都可以直接指定大小
  • ViewGroup 的layout

    • 重写ViewGroup的onLayout的方法来决定放置的位置
      代码还没有写出来
  • 自定义view

    • 几个重要的回调方法

      • onFinishInflate() 从XML加载后返回
      • onSizeChanged() 组件大小改变时回调 当然在第一次测量后那么组件大小也是发生了改变,也会调用这个方法
      • onMeasure() 回调这个方法来进行测量控件的大小和获取测量模式,并且支持wrap_content属性值

      • onLayout 回调这个方法来控制控件的显示位置(目前为止还不知道怎么使用)

      • onTouchEvent() 回调这个方法来处理触摸事件
    • 自定义控件的三种形式

      • 继承已有的view 通过重写onDraw()方法来进行扩展
        主要来进行修改这个view的一些属性,例如颜色,背景,动画等
      • 通过组合view来实现一个新的viewGroup
        通过view的组合来组成一个viewGroup模板,以便以后的多次使用,分下面几步进行

        1. 在values/ 创建attrs.xml 文件,用来定义自定义的属性集合

          <?xml version="1.0" encoding="utf-8"?>
          <resources>
              <declare-styleable name="TopBar">
                  <attr name="leftButtonColor" format="color|reference"/>
                  <attr name="rightButtonColor" format="color|reference"/>
                  <attr name="TitleText" format="string"/>
              </declare-styleable>
          </resources>
        2. 使用这些属性,在XML文件中

          <com.zh.young.androidreview.custom_control.TopBar android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:custom="http://schemas.android.com/apk/res-auto"
                  custom:leftButtonColor="@color/colorAccent"
                  custom:rightButtonColor="@color/colorPrimaryDark"
                  custom:TitleText="测试"
                  android:background="@color/colorAccent"
                  android:id="@+id/topbar"
          />
          

        **这里注意,如果要使用自定义的属性,那么就要引入自定义的命名空间
        xmlns:custom=”http://schemas.android.com/apk/res-auto”
        除了custom为自定义的命名空间名字外,其他的都是这种形式,无需改变**

        1. 在自定义的控件的Java程序中获取所有的属性

          public void getAttrsCollection(AttributeSet attrs){
                  ta = getContext().obtainStyledAttributes(attrs, R.styleable.TopBar);
          
                  topBar_leftButtonColor = ta.getColor(R.styleable.TopBar_leftButtonColor, 0);
                  topBar_rightButtonColor = ta.getColor(R.styleable.TopBar_rightButtonColor, 0);
                  topBar_titleText = ta.getString(R.styleable.TopBar_TitleText);
                  //不要忘记回收这个TypeArray 避免出错  虽然不知道会出现什么错误
                  ta.recycle();
          }
        2. 为所有的控件设置属性

          private void setAttrs() {
                  leftButton.setBackgroundColor(topBar_leftButtonColor);
                  rightButton.setBackgroundColor(topBar_rightButtonColor);
                   title.setText(topBar_titleText);
          }
        3. 为 Button设置点击事件,使用面向接口编程的思想来进行,这些具体实现由用户来实现
          “`
          public void setOnButtonClickListener(final OnButtonClickListener onButtonClickListener){
          leftButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View view) {
        onButtonClickListener.leftClick();
        }
        });

        rightButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
        onButtonClickListener.rightClick();
        }
        });

        }

      public interface OnButtonClickListener{
      void leftClick();
      void rightClick();
      }
      “`

      • 在绘制弧度时候的发现,弧度的开始位置应该是从水平线的右边开始是0度,然后顺时针增长是正值,逆时针增长是负值,终点的弧度值的确定应该是第一个弧度值加第二个弧度值
      • 自定义ViewGroup实现粘性效果

        1. 创建一个ViewGroup继承自ViewGroup

              public class CustomNewViewGroup extends ViewGroup {}
        2. 重写onMeasure方法,通知子view进行测绘

              @Override
              protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                  int count = getChildCount();
                  for(int i = 0;i < count;i++){
                       measureChild(getChildAt(i),widthMeasureSpec,heightMeasureSpec);
                  }
                  }
        3. 重写onLayout方法进行放置view

              @Override
           protected void onLayout(boolean changed, int l, int t, int r, int b) {
               int count = getChildCount();
              //设置自身的宽高
              mScreenHeight = getHeight();
              MarginLayoutParams layoutParams = (MarginLayoutParams)getLayoutParams();
              layoutParams.height = mScreenHeight * count;
              setLayoutParams(layoutParams);
          
              //放置子view,每个占一屏
              for(int i = 0;i < count;i++){
                   View child = getChildAt(i);
                  child.layout(0,i * mScreenHeight,getWidth(),(i+1) * mScreenHeight);
              }
              }
        4. 重写onTouchEvent()方法支持滑动事件并且重写computeScroll()方法支持scroller的回滑

              @Override
              public boolean onTouchEvent(MotionEvent event) {
          
              switch (event.getAction()){
                  case MotionEvent.ACTION_DOWN :
                      //滑动的起点记录
                      mStart = (int) getScaleY();
                      Log.i(TAG,getScrollY()+"");
                      mLastY = (int) event.getY();
                      //Log.i(TAG,"已经按下  mLastY = "+mLastY);
                  break;
                  case MotionEvent.ACTION_MOVE :
                      int newY = (int) event.getY();
                      //Log.i(TAG,"newY   :    " + newY + "--------------"+"mLastY    :    " + mLastY);
                      scrollBy(0, mLastY - newY);
                      mLastY = newY;
                      break;
                  case MotionEvent.ACTION_UP :
                      int scroll = (int) (getScrollY() - mStart);
                      Log.i(TAG,"scroll = " + scroll + "----------" + "getScaleY  = " + getScrollY());
                      if(scroll > 0){
                          if(scroll < mScreenHeight / 3) {  //如果滑动的距离小于1/3的屏幕高度,那么弹回当前页
                              mScroller.startScroll(
                                  0,getScrollY(),
                                  0,-scroll
                          );
                          Log.i(TAG,"执行了");
          
                      }else{
                       mScroller.startScroll(0,getScrollY(),0,mScreenHeight-scroll);
                      }
          
                  invalidate();
              }
              break;
              }
               return true;
              }
          
              @Override
              public void computeScroll() {
                  super.computeScroll();
                  if(mScroller.computeScrollOffset()){
                      scrollTo(0,mScroller.getCurrY());
                      postInvalidate();
              }
              }

4 ListView的使用技巧

  • 常用的两种提高ListView效率的两种方法,在适配器中,继承BaseAdapter,主要是getView()方法

    1. 使用ViewHolder避免每次都进行findviewById(),通过上面的学习,知道Android架构对于每次使用findViewById()都是通过深度优先遍历获取的,这样就增加了系统消耗,所以出现了ViewHolder来避免每次的深度优先的遍历
    2. 复用View,因为每次的view的创建都要经过measure->layout->draw几个方面,消耗系统资源,所以可以通过复用这些已经申请好的内容来提高系统效率。
      • 下面通过代码来看一下用法,这段代码混合了convertView和ViewHolder
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
        
                ViewHolder viewHolder;  //作为后面使用的变量,提前声明出来
                //如果convertView == null 那么一定是第一次使用,那么就需要做初始化工作,这个初始化工作,就在这个if里面做
                if(convertView == null){
                    convertView = View.inflate(getApplicationContext(),R.layout.listview_item,null);
                    viewHolder = new ViewHolder();
                    viewHolder.tv_test = (TextView) convertView.findViewById(R.id.tv_test);
                    viewHolder.btn_test = (Button) convertView.findViewById(R.id.btn_test);
                    //将获取奥的控件ID都存入到convertView中,下次就可以直接使用了
                    convertView.setTag(viewHolder);
        
                }else{
                    //这里就可以从convertView里面讲viewHolder取出来,就避免了再次进行深度遍历了
                    viewHolder = (ViewHolder) convertView.getTag();
                }
        
                //然后就可以对view进行操作了
                return convertView;
            }
        
             private class ViewHolder{
                    TextView tv_test;
                 Button btn_test;
                }
            }
  • 下面是进行ListView的一些修饰工作了,for instance 设置条目的分割线,背景啊,点击的时候的颜色啊等等,是视觉上面的优化
    1. 设置项目的分割线
      1. 在XML文件中指定分割线

        <ListView
        android:divider="@null"
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

        这里说一个小知识点:就是@null这个标示,在可以使用reference的属性中(这个reference就是在自定义属性中指定的format=”reference”)可以使用@null表示不使用,例如上面这个属性,还有background等等
      2. 在Java程序中指定

        listView.setDivider(null);
    2. 隐藏LIstView的滚动条
      在使用LIstView的时候默认情况下会在右边显示滚动条,如果想不显示,那么就有两种方法
      1. 在XML文件中指定

        android:scrollbars="none"
      2. 很可惜没有找到在Java代码中如何修改
    3. 取消ListView的Item的点击效果 说白了就是在选中时候的效果

      android:listSelector="@color/colorPrimary"

      话说我怎么就没点出来颜色呢?!!!!反正都是白的,不知道怎么变颜色
    4. 设置ListView默认显示在第几项
      喏,只能这么设置了

      listView.setSelection(4);
      //4代表第一个显示的条目,不知道你有没有联想到ViewPager这个工具,一个横向的ListView几乎是,做那什么横着的广告的那个玩意儿,就可以使用类似这样的属性,表示无限循环,就是将第一个显示的位置设置在Integer,max/2的位置,累死你滑不出去,就是这个东西,都是骗人的,哪有无限循环啊,话说你也可以弄个竖着的无线广告,看看你客户的反应,哈哈哈,爽爆
    5. 动态修改ListView的数据
      • 这个举要结合这分页加载这个东东说了,分页加载的意思是,我一次从服务器那二十条数据,显示在客户端(ListView),那么每次这个数据要展示我就要更新数据适配器吧,我拿什么来更新呢?我让你不显示,不让你睡觉!!

        myAdapter.notifyDataSetChanged();

        当你把数据准备好的时候(你当然是在子线程加载的数据,不然你的主线程会埋怨你的,╮(╯▽╰)╭太难伺候),告诉Handler,我已经准备数据了,你可以进行UI更新了,OK,你就可以调用这个方法了
    6. 遍历ListView中的所有Item
      如果你想要拿到一些(个)View,那么通过这个方法就可以了

      listView.getChildAt(index);
    7. 如果你的ListView现在是空的,或者是没有网络的情况下加载不出数据,那么为了友好的显示界面,那么你就需要为你的user显示一张可爱的图片,来拉住你user的心,让她往死了拉去刷新(对,平常我都是这么干的!!)
      1. 在你设置ListView的activity中设置一张图片ImageView,但是这张图片默认是不显示的,你可以试试,原理我还真不知道,如果哪位童鞋知道,请!告!知!满足我这颗渴望的心,下面看代码
        1. 在XML文件中设置

          <ImageView
          android:id="@+id/image_view"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:src="@drawable/error_image"
          />
        2. 在Java程序中设置

          listView.setEmptyView(findViewById(R.id.image_view));
    8. LIstView的滑动监听
      1. 在医生的书中,看到了这几种监听,手势的监听GestureDetector,滑动速度的监听VelocityTrancker,触摸监听onTouchEvent,滑动监听onScrollListener.我好像现在只会三种╮(╯▽╰)╭,一个是GestureDetector、onTouchEvent,onScrollListener,下面就这几项来介绍一下吧,那么滑动速度还是要补滴,但是好像要吃饭了哎,只能明天更新了,拜~~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值