Android自定义View的三种实现方式总结

在毕设项目中多处用到自定义控件,一直打算总结一下自定义控件的实现方式,今天就来总结一下吧。在此之前学习了郭霖大神博客上面关于自定义View的几篇博文,感觉受益良多,本文中就参考了其中的一些内容。

总结来说,自定义控件的实现有三种方式,分别是:组合控件、自绘控件和继承控件。下面将分别对这三种方式进行介绍。

(一)组合控件

组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件。比如很多应用中普遍使用的标题栏控件,其实用的就是组合控件,那么下面将通过实现一个简单的标题栏自定义控件来说说组合控件的用法。

1、新建一个Android项目,创建自定义标题栏的布局文件title_bar.xml:

<?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="wrap_content"
  android:background="#0000ff" >

  <Button
    android:id="@+id/left_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_margin="5dp"
    android:background="@drawable/back1_64" />

  <TextView
    android:id="@+id/title_tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="这是标题"
    android:textColor="#ffffff"
    android:textSize="20sp" />

</RelativeLayout>

可见这个标题栏控件还是比较简单的,其中在左边有一个返回按钮,背景是一张事先准备好的图片back1_64.png,标题栏中间是标题文字。

2、创建一个类TitleView,继承自RelativeLayout:

public class TitleView extends RelativeLayout {

  // 返回按钮控件
  private Button mLeftBtn;
  // 标题Tv
  private TextView mTitleTv;

  public TitleView(Context context, AttributeSet attrs) {
    super(context, attrs);

    // 加载布局
    LayoutInflater.from(context).inflate(R.layout.title_bar, this);

    // 获取控件
    mLeftBtn = (Button) findViewById(R.id.left_btn);
    mTitleTv = (TextView) findViewById(R.id.title_tv);

  }

  // 为左侧返回按钮添加自定义点击事件
  public void setLeftButtonListener(OnClickListener listener) {
    mLeftBtn.setOnClickListener(listener);
  }

  // 设置标题的方法
  public void setTitleText(String title) {
    mTitleTv.setText(title);
  }
}

在TitleView中主要是为自定义的标题栏加载了布局,为返回按钮添加事件监听方法,并提供了设置标题文本的方法。

3、在activity_main.xml中引入自定义的标题栏:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/main_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >

  <com.example.test.TitleView
    android:id="@+id/title_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >
  </com.example.test.TitleView>

</LinearLayout>

4、在MainActivity中获取自定义的标题栏,并且为返回按钮添加自定义点击事件:

private TitleView mTitleBar;
     mTitleBar = (TitleView) findViewById(R.id.title_bar);

    mTitleBar.setLeftButtonListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
        Toast.makeText(MainActivity.this, "点击了返回按钮", Toast.LENGTH_SHORT)
            .show();
        finish();
      }
    });

5、运行效果如下:

   

这样就用组合的方式实现了自定义标题栏,其实经过更多的组合还可以创建出功能更为复杂的自定义控件,比如自定义搜索栏等。

 (二)自绘控件

自绘控件的内容都是自己绘制出来的,在View的onDraw方法中完成绘制。下面就实现一个简单的计数器,每点击它一次,计数值就加1并显示出来。

1、创建CounterView类,继承自View,实现OnClickListener接口:

public class CounterView extends View implements OnClickListener {

  // 定义画笔
  private Paint mPaint;
  // 用于获取文字的宽和高
  private Rect mBounds;
  // 计数值,每点击一次本控件,其值增加1
  private int mCount;

  public CounterView(Context context, AttributeSet attrs) {
    super(context, attrs);

    // 初始化画笔、Rect
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mBounds = new Rect();
    // 本控件的点击事件
    setOnClickListener(this);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    mPaint.setColor(Color.BLUE);
    // 绘制一个填充色为蓝色的矩形
    canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);

    mPaint.setColor(Color.YELLOW);
    mPaint.setTextSize(50);
    String text = String.valueOf(mCount);
    // 获取文字的宽和高
    mPaint.getTextBounds(text, 0, text.length(), mBounds);
    float textWidth = mBounds.width();
    float textHeight = mBounds.height();

    // 绘制字符串
    canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2
        + textHeight / 2, mPaint);
  }

  @Override
  public void onClick(View v) {
    mCount ++;

    // 重绘
    invalidate();
  }

}

2、在activity_main.xml中引入该自定义布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/main_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >

  <com.example.test.CounterView
    android:id="@+id/counter_view"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_gravity="center_horizontal|top"
    android:layout_margin="20dp" />

</LinearLayout>

3、运行效果如下:

(三)继承控件

就是继承已有的控件,创建新控件,保留继承的父控件的特性,并且还可以引入新特性。下面就以支持横向滑动删除列表项的自定义ListView的实现来介绍。

1、创建删除按钮布局delete_btn.xml,这个布局是在横向滑动列表项后显示的:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="#FF0000"
  android:padding="5dp"
  android:text="删除"
  android:textColor="#FFFFFF"
  android:textSize="16sp" >

</Button>

2、创建CustomListView类,继承自ListView,并实现了OnTouchListener和OnGestureListener接口:

public class CustomListView extends ListView implements OnTouchListener,
    OnGestureListener {

  // 手势动作探测器
  private GestureDetector mGestureDetector;

  // 删除事件监听器
  public interface OnDeleteListener {
    void onDelete(int index);
  }

  private OnDeleteListener mOnDeleteListener;

  // 删除按钮
  private View mDeleteBtn;

  // 列表项布局
  private ViewGroup mItemLayout;

  // 选择的列表项
  private int mSelectedItem;

  // 当前删除按钮是否显示出来了
  private boolean isDeleteShown;

  public CustomListView(Context context, AttributeSet attrs) {
    super(context, attrs);

    // 创建手势监听器对象
    mGestureDetector = new GestureDetector(getContext(), this);

    // 监听onTouch事件
    setOnTouchListener(this);
  }

  // 设置删除监听事件
  public void setOnDeleteListener(OnDeleteListener listener) {
    mOnDeleteListener = listener;
  }

  // 触摸监听事件
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    if (isDeleteShown) {
      hideDelete();
      return false;
    } else {
      return mGestureDetector.onTouchEvent(event);
    }
  }

  @Override
  public boolean onDown(MotionEvent e) {
    if (!isDeleteShown) {
      mSelectedItem = pointToPosition((int) e.getX(), (int) e.getY());
    }
    return false;
  }

  @Override
  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
      float velocityY) {
    // 如果当前删除按钮没有显示出来,并且x方向滑动的速度大于y方向的滑动速度
    if (!isDeleteShown && Math.abs(velocityX) > Math.abs(velocityY)) {
      mDeleteBtn = LayoutInflater.from(getContext()).inflate(
          R.layout.delete_btn, null);

      mDeleteBtn.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
          mItemLayout.removeView(mDeleteBtn);
          mDeleteBtn = null;
          isDeleteShown = false;
          mOnDeleteListener.onDelete(mSelectedItem);
        }
      });

      mItemLayout = (ViewGroup) getChildAt(mSelectedItem
          - getFirstVisiblePosition());

      RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
          LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
      params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
      params.addRule(RelativeLayout.CENTER_VERTICAL);

      mItemLayout.addView(mDeleteBtn, params);
      isDeleteShown = true;
    }

    return false;
  }

  // 隐藏删除按钮
  public void hideDelete() {
    mItemLayout.removeView(mDeleteBtn);
    mDeleteBtn = null;
    isDeleteShown = false;
  }

  public boolean isDeleteShown() {
    return isDeleteShown;
  }

  /**
   * 后面几个方法本例中没有用到
   */
  @Override
  public void onShowPress(MotionEvent e) {

  }

  @Override
  public boolean onSingleTapUp(MotionEvent e) {
    return false;
  }

  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
      float distanceY) {
    return false;
  }

  @Override
  public void onLongPress(MotionEvent e) {

  }

}

3、定义列表项布局custom_listview_item.xml,它的结构很简单,只包含了一个TextView:

<?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:descendantFocusability="blocksDescendants" >

  <TextView
    android:id="@+id/content_tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:layout_margin="30dp"
    android:gravity="center_vertical|left" />

</RelativeLayout>

4、定义适配器类CustomListViewAdapter,继承自ArrayAdapter<String>:

public class CustomListViewAdapter extends ArrayAdapter<String> {

  public CustomListViewAdapter(Context context, int textViewResourceId,
      List<String> objects) {
    super(context, textViewResourceId, objects);
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    View view;

    if (convertView == null) {
      view = LayoutInflater.from(getContext()).inflate(
          R.layout.custom_listview_item, null);
    } else {
      view = convertView;
    }

    TextView contentTv = (TextView) view.findViewById(R.id.content_tv);
    contentTv.setText(getItem(position));

    return view;
  }

}

5、在activity_main.xml中引入自定义的ListView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/main_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical" >

  <com.example.test.CustomListView
    android:id="@+id/custom_lv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

6、在MainActivity中对列表做初始化、设置列表项删除按钮点击事件等处理:

public class MainActivity extends Activity {

  // 自定义Lv
  private CustomListView mCustomLv;
  // 自定义适配器
  private CustomListViewAdapter mAdapter;
  // 内容列表
  private List<String> contentList = new ArrayList<String>();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);

    initContentList();

    mCustomLv = (CustomListView) findViewById(R.id.custom_lv);
    mCustomLv.setOnDeleteListener(new OnDeleteListener() {

      @Override
      public void onDelete(int index) {
        contentList.remove(index);
        mAdapter.notifyDataSetChanged();
      }
    });

    mAdapter = new CustomListViewAdapter(this, 0, contentList);
    mCustomLv.setAdapter(mAdapter);
  }

  // 初始化内容列表
  private void initContentList() {
    for (int i = 0; i < 20; i++) {
      contentList.add("内容项" + i);
    }
  }

  @Override
  public void onBackPressed() {
    if (mCustomLv.isDeleteShown()) {
      mCustomLv.hideDelete();
      return;
    }
    super.onBackPressed();
  }

}

7、运行效果如下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

时间: 2017-02-14

Android自定义Adapter的ListView的思路及代码

在开发中,我们经常使用到ListView这个控件.Android的API也提供了许多创建ListView适配器的快捷方式.例如ArrayAdapter.SimpleAdapter和SimpleCursorAdapter等.但你是否发现,如果采用这些系统自带的适配器,对于事件的响应只能局限在一个行单位.假设一行里面有一个按钮和一个图片控件,它们之间的响应操作是不一样的.若采用系统自带的适配器,就不能精确到每个控件的响应事件.这时,我们一般采取自定义适配器来实现这个比较精确地请求. ListView

android imageview图片居中技巧应用

做UI布局,尤其是遇到比较复杂的多重LinearLayout嵌套,常常会被一些比较小的问题困扰上半天,比如今天在使用ImageView的时候,想让其居中显示,可是无论怎样设置layout_gravity属性,都无法达到效果,部分代码如下: [java] 复制代码 代码如下: <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:

Android中实现Webview顶部带进度条的方法

写这篇文章,做份备忘,简单滴展示一个带进度条的Webview示例,进度条位于Webview上面. 示例图如下: 主Activity代码: 复制代码 代码如下: package com.droidyue.demo.webviewprogressbar; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.vi

android实现上下滚动的TextView

一 说明    这里重要应用类 AutoTextView,这是一个自定义的类,继承至TextSwitcher,下面临 AutoTextView类做简要说明: 1. 该类应用的重点,在于设置两个动画, setInAnimation(...)  和 setOutAnimation(...),分离是文字进入的动画和文字退出的动画: 2. 类中定义了一个外部类-Rotate3dAnimation,重要靠该类实现文字进出动画,该外部类继承至Animation.说来偶合,这个恰好是在apiDemo中看到了,

android listview优化几种写法详细介绍

这篇文章只是总结下getView里面优化视图的几种写法,就像孔乙己写茴香豆的茴字的几种写法一样,高手勿喷,勿笑,只是拿出来分享,有错误的地方欢迎大家指正,谢谢. listview Aviewthatshowsitemsinaverticallyscrollinglist. 一个显示一个垂直的滚动子项的列表视图在android开发中,使用listview的地方很多,用它来展现数据,成一个垂直的视图.使用listview是一个标准的适配器模式,用数据--,界面--xml以及适配器--adapter,

Android中实现监听ScrollView滑动事件

时候我们需要监听ScroView的滑动情况,比如滑动了多少距离,是否滑到布局的顶部或者底部.可惜的是SDK并没有相应的方法,不过倒是提供了一个 复制代码 代码如下: protected void onScrollChanged(int x, int y, int oldx, int oldy) 方法,显然这个方法是不能被外界调用的,因此就需要把它暴露出去,方便使用.解决方式就是写一个接口, 复制代码 代码如下: package com.example.demo1;    public inter

android自定义进度条渐变色View的实例代码

最近在公司,项目不是很忙了,偶尔看见一个兄台在CSDN求助,帮忙要一个自定义的渐变色进度条,我当时看了一下进度条,感觉挺漂亮的,就尝试的去自定义view实现了一个,废话不说,先上图吧! 这个自定义的view,完全脱离了android自带的ProgressView,并且没使用一张图片,这样就能更好的降低程序代码上的耦合性! 下面我贴出代码  ,大概讲解一下实现思路吧! 复制代码 代码如下: package com.spring.progressview; import android.conten

android TextView设置中文字体加粗实现方法

英文设置加粗可以在xml里面设置: 复制代码 代码如下: <SPAN style="FONT-SIZE: 18px">android:textStyle="bold"</SPAN> 英文还可以直接在String文件里面直接这样填写: 复制代码 代码如下: <string name="styled_text">Plain, <b>bold</b>, <i>italic</

android开发教程之listview使用方法

首先是布局文件,这里需要两个布局文件,一个是放置列表控件的Activity对应的布局文件 main.xml,另一个是ListView中每一行信息显示所对应的布局  list_item.xml    这一步需要注意的问题是ListView 控件的id要使用Android系统内置的 android:id="@android:id/list"   [注意形式] main.xml 复制代码 代码如下: <?xml version="1.0" encoding=&quo

Android下拉刷新ListView——RTPullListView(demo)

下拉刷新在越来越多的App中使用,已经形成一种默认的用户习惯,遇到列表显示的内容时,用户已经开始习惯性的拉拉.在交互习惯上已经形成定性.之前在我的文章<IOS学习笔记34-EGOTableViewPullRefresh实现下拉刷新>中介绍过如何在IOS上实现下拉刷新的功能.今天主要介绍下在Android上实现下拉刷新的Demo,下拉控件参考自Github上开源项目PullToRefresh,并做简单修改.最终效果如下:                         工程结构如下: 使用过程中

android下拉刷新ListView的介绍和实现代码

大致上,我们发现,下拉刷新的列表和一般列表的区别是,当滚动条在顶端的时候,再往下拉动就会把整个列表拉下来,显示出松开刷新的提示.由此可以看出,在构建这个下拉刷新的组件的时候,只用继承ListView,然后重写onTouchEvent就能实现.还有就是要能在xml布局文件中引用,还需要一个参数为Context,AttributeSet的构造函数. 表面上的功能大概就这些了.另一方面,刷新的行为似乎还没有定义,在刷新前做什么,刷新时要做什么,刷新完成后要做什么,这些行为写入一个接口中,然后让组件去实

Android下拉刷新上拉加载控件(适用于所有View)

前面写过一篇关于下拉刷新控件的文章下拉刷新控件终结者:PullToRefreshLayout,后来看到好多人还有上拉加载更多的需求,于是就在前面下拉刷新控件的基础上进行了改进,加了上拉加载的功能.不仅如此,我已经把它改成了对所有View都通用!可以随心所欲使用这两个功能~~ 我做了一个大集合的demo,实现了ListView.GridView.ExpandableListView.ScrollView.WebView.ImageView.TextView的下拉刷新和上拉加载.后面会提供demo的

Android下拉刷新框架实现代码实例

前段时间项目中用到了下拉刷新功能,之前在网上也找到过类似的demo,但这些demo的质量参差不齐,用户体验也不好,接口设计也不行.最张没办法,终于忍不了了,自己就写了一个下拉刷新的框架,这个框架是一个通用的框架,效果和设计感觉都还不错,现在分享给各位看官. 一. 关于下拉刷新 下拉刷新这种用户交互最早由twitter创始人洛伦•布里切特(Loren Brichter)发明,有理论认为,下拉刷新是一种适用于按照从新到旧的时间顺序排列feeds的应用,在这种应用场景中看完旧的内容时,用户会很自然地下

Android下拉刷新控件PullToRefresh实例解析

Android中很多时候都会用到上下拉刷新,这是一个很常用的功能,Android的v4包中也为我们提供了一种原生的下拉刷新控件--SwipeRefreshLayout,可以用它实现一个简洁的刷新效果,但今天我们的主角并不是它,而是一个很火的第三方的上下拉刷新控件--PullToRefresh.PullToRefresh包括PullToRefreshScrollView.PullToRefreshListView.PullToRefreshGridView等等很多为我们提供的控件,我们可以在xml

Android下拉刷新SwipeRefreshLayout控件使用方法

我们知道现在的material design十分的流行,而SwipeRefreshLayout 就是原生的一个效果(也是Facebook下拉刷新效果)SwipeRefreshLayout 是一个下拉刷新控件,几乎可以包裹一个任何可以滚动的内容(ListView GridView ScrollView RecyclerView),可以自动识别垂直滚动手势.使用起来非常方便. 他强大到可以有很多个子view来继承,进而可以实现不同效果(博主未研究) 话不多说先来看图 看到上面那个绿色的小圆圈没?就是

Android下拉刷新PtrFrameLayout的使用实例代码

1.介绍: 可以包含所有的控件 :ListView, GridView, ScrollView, FrameLayout, 甚至 TextView. 可以自定义刷新头(这点非常实用) 使用简单方便 不足就是不支持上拉加载. 2.使用 首先添加依赖到项目 compile 'in.srain.cube:ultra-ptr:1.0.11' 在Xml中使用 <in.srain.cube.views.ptr.PtrFrameLayout xmlns:android="http://schemas.a

Android下拉刷新官方版

网上关于下拉刷新的文章也不少,不过都太长了.恰好发现了官方的下拉刷新库,而且效果还是不错的,简洁美观,用得也挺方便. 下面是效果图: 我的好友原来是空的,刷新后多了两个. 使用还是挺方便的,在布局文件中加入SwipeRefreshLayout ,这个就是下拉刷新的布局. 我在SwipeRefreshLayout的里面还加入了一个ListView 因为我主要用下拉刷新更新了listView里面的内容 . 布局文件: <RelativeLayout xmlns:android="http://

Android下拉刷新以及GridView使用方法详解

GridView是类似于ListView的控件,只是GridView可以使用多个列来呈现内容,而ListView是以行为单位,所以用法上是差不多的. 主布局文件,因为要做下拉刷新,所以加了一个ProgressBar,GridView的numColumns属性是指每一行有多少列 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://sch

Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能(附源码)

最近项目中需要用到ListView下拉刷新的功能,一开始想图省事,在网上直接找一个现成的,可是尝试了网上多个版本的下拉刷新之后发现效果都不怎么理想.有些是因为功能不完整或有Bug,有些是因为使用起来太复杂,十全十美的还真没找到.因此我也是放弃了在网上找现成代码的想法,自己花功夫编写了一种非常简单的下拉刷新实现方案,现在拿出来和大家分享一下.相信在阅读完本篇文章之后,大家都可以在自己的项目中一分钟引入下拉刷新功能. 首先讲一下实现原理.这里我们将采取的方案是使用组合View的方式,先自定义一个布局