2Android-UI(自定义控件&ListView)

一万年来谁著史,三千里外欲封侯。这篇文章主要讲述2Android-UI(自定义控件& ListView)相关的知识,希望能为你提供帮助。
2.4、系统控件不够用创建自定义控件控件的和布局的集成结构:

2Android-UI(自定义控件&ListView)

文章图片

 
所有的控件都是间接或者直接集成View的
所有的布局都是直接或者间接继承自ViewGroup的
View是android种最基本的一种UI组件
可以再屏幕上进行创建任何布局或者各种事件
 
所以使用的各种控件其实就是再View的基础上添加了特有的功能
ViewGroup是一个特殊的View
他可以包含很对的子View和ViewGroup,是一个用于放置控件和布局的容器
 
当系统提供的控件不满足开发时,可以自己创建自定义的控件
 
1、引入布局  实现标题栏的代码进行解析
 
【2Android-UI(自定义控件& ListView)】新建一个tittle.xml的布局文件
2Android-UI(自定义控件&ListView)

文章图片

< ?xml version="1.0" encoding="utf-8"?> < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> < Button android:id="@+id/title_back" android:layout_width="wrap_content" android:layout_height="wrap_content"android:layout_margin="5dp" android:text="back" android:textColor="#fff" android:background="#90a" /> < TextView android:id="@+id/title_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:text="Title message" android:textColor="#90a" android:textSize="24sp" /> < Button android:id="@+id/title_edit" android:layout_width="wrap_content" android:layout_height="wrap_content"android:layout_margin="5dp" android:text="Edit" android:textColor="#fff" android:background="#90a" /> < /LinearLayout>

这里使用android:background用于只当控件的背景(可以是图片可以是颜色)
android:layout_margin:用于指定控件再上下左右方向的便宜距离
 
效果:
2Android-UI(自定义控件&amp;ListView)

文章图片

 
再first_layout种进行使用这个标题:
此时直接使用< include> 这个标签进行引用
< ?xml version="1.0" encoding="utf-8"?> < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> < include layout="@layout/tittle" /> < /LinearLayout>

 
 
2Android-UI(自定义控件&amp;ListView)

文章图片

同时还需要将系统自带的标题栏给隐藏
@Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("FirstActivity====", String.valueOf(getTaskId())); setContentView(R.layout.first_layout); ActionBar actionBar = getSupportActionBar(); if (actionBar != null){ actionBar.hide(); }}

 
使用getSupportActionBar()方法获得ActionBar的实例
再调用hide()方法进行隐藏
 
2、创建自定义的控件引入布局的技巧解决了重复编写代码的问题
但是一个布局种有一些控件要求能够响应事件
还需要再每个活动中为这些控件单独的编写注册的代码
 
测试标题栏控件的实现:
新建类继承LinerLayout
public class Tittle extends LinearLayout {public Tittle(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.tittle,this); Button buttonBack = (Button) findViewById(R.id.title_back); Button buttonEdit = (Button) findViewById(R.id.title_edit); buttonBack.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //退出 ((Activity)getContext()).finish(); } }); buttonEdit.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getContext(),"you click EDIT Button",Toast.LENGTH_LONG).show(); } }); } }

重写LinearLayout构造函数
在布局引入Tittle时就会调用这个函数
在构造函数中对标题栏进行动态加载
 
通过LayoutInflater的from()方法可以构建处一个LayoutInflater对象
然后调用inflate()方法可以动态加载一个布局文件,两个参数:
1、要加载布局文件的id
2、加载好布局在添加一个父布局
 
然后再xml文件中进行引用:
< ?xml version="1.0" encoding="utf-8"?>
< LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

< com.example.ccrr.myapplication.Tittle
android:layout_width="match_parent"
android:layout_height="wrap_content">
< /com.example.ccrr.myapplication.Tittle>
< /LinearLayout>

 
需要指明控件的完整类名
点击BACK时退出
点击EDIT时出现提示信息
2Android-UI(自定义控件&amp;ListView)

文章图片

 
 
  2.5、ListView是Android中最长使用的控件之一
由于手机的屏幕有限,显示的数据内容不多,当有大量的数据需要展示的时候
可以使用ListView进行实现
类似QQ的好友列表...
 
1、简单实现在first_layout中: 
< ?xml version="1.0" encoding="utf-8"?> < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> < ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="wrap_content"> < /ListView> < /LinearLayout>

 
  简单的加入该控件
 
MainActivityz中:
public class MainActivity extends AppCompatActivity {private String [] data = https://www.songbingjia.com/android/{"apple","Banana","Orange","Water", "pear","Grape","pineapple","strawberry","cerry","Mango", "1","2","3","4","5","6","7" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.firstlayout); ActionBar actionBar = getSupportActionBar(); if (actionBar != null){ actionBar.hide(); }ArrayAdapter< String> adapter = new ArrayAdapter< String> (MainActivity.this, android.R.layout.simple_list_item_1,data); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); } }

 
使用数组提供数据进行显示
使用适配器ArrayAdapter来实现(指定类型)
有多个构造函数的重载
此时的参数:
1、当前的上下文
2、ListView的子布局id,这是Android内嵌的布局文件
3、数据
 
最后使用ListView的setAdapter()方法将构建好的适配器对象传进去
此时数据和ListView之间就建立了联系
2Android-UI(自定义控件&amp;ListView)

文章图片

 
2、定制界面实现图片加数据的显示
定义一个实体类作为ListView的适配类型
public class Fruit { //name:说过名字 private String name; //商品的图片id位置 private int id; public Fruit(String name, int id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } }

 
 
新建:
2Android-UI(自定义控件&amp;ListView)

文章图片

< ?xml version="1.0" encoding="utf-8"?> < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> < ImageView android:src="https://www.songbingjia.com/android/@drawable/qq" android:id="@+id/image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> < TextView android:id="@+id/text_view" android:layout_marginLeft="10dp" android:layout_gravity="center_vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" /> < /LinearLayout>

 
使用ImageView用于保存图片
使用TextView用于显示水果的名称
 
自定义一个适配器继承ArrayAdapter,并将泛型指定为Fruit类
public class FruitAdapter extends ArrayAdapter< Fruit> {private int resourceId; public FruitAdapter(Context context,int textViewResourceId, List< Fruit> objects) { super(context, textViewResourceId, objects); resourceId=textViewResourceId; }@NonNull @Override public View getView(int position, View convertView, ViewGroup parent) {Fruit fruit = getItem(position); //获取当前项Fruit实例 View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false); ImageView imageView = (ImageView) view.findViewById(R.id.image_view); TextView textView = (TextView) view.findViewById(R.id.text_view); imageView.setImageResource(fruit.getId()); textView.setText(fruit.getName()); return view; } }

重写父类的一组构造函数,用于将上下文、ListView子项布局的id和数据都传进来
 
由重写geiView()方法,将每个子项被滚动到屏幕内的时候会被调用
在方法中首先得到当前的Fruit的实例,然后再LayoutInflater来为这个子项目加载到我们传入的布局
第三个参数:指定为false表示只让我们在父布局中声明的layout属性生效,但不为这个View添加父布局
因为一旦有了父布局之后,他就不能再添加到ListView中。
 
再View中findViewById()方法分别用于获取ImageView和TextView的实例,并且调用他们的
setImageResource()和setText()方法来设置图片和文字
此时自定义的适配器就完成了
 
再MianActivity中
public class MainActivity extends AppCompatActivity {private List< Fruit> fruitList = new ArrayList< > (); private void initFruit(){ for (int i =0; i< 6; i++){ Fruit apple = new Fruit("Apple1",R.drawable.qq); fruitList.add(apple); Fruit apple1 = new Fruit("Apple2",R.drawable.qq); fruitList.add(apple1); Fruit apple2 = new Fruit("Apple3",R.drawable.qq); fruitList.add(apple2); Fruit apple3 = new Fruit("Apple4",R.drawable.qq); fruitList.add(apple3); Fruit apple4 = new Fruit("Apple5",R.drawable.qq); fruitList.add(apple4); Fruit apple5 = new Fruit("Apple6",R.drawable.qq); fruitList.add(apple5); Fruit apple6 = new Fruit("Apple7",R.drawable.qq); fruitList.add(apple6); }}@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.firstlayout); ActionBar actionBar = getSupportActionBar(); if (actionBar != null){ actionBar.hide(); }initFruit(); FruitAdapter adapter = new FruitAdapter( MainActivity.this,R.layout.fruit_item,fruitList); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); } }

这里使用initFruit()进行初始化数据,数据可能来源于网络和数据库中。
初始化时将图片和名字传入到实例中。
然后将适配器传入ListView中即可进行显示,
2Android-UI(自定义控件&amp;ListView)

文章图片

此时定制的页面比较简单,只要修改对应的文件内容就可以制作出各种复杂的界面。
 
3、提升ListView的运行效率  对于ListView很难使用的原因时他可以有很多的细节可以进行优化
运行效率就是重要之一
目前使用的ListView的运行效率是比较低的
 
FruitAdaper的getView()方法中,每次都将布局重新加载一遍
当ListView快速滚动的时候就会成为性能的瓶颈
 
仔细观察可以看出getView()方法中还有一个参数convertView
这个参数用于将之前加载好的布局进行缓存,以便重用
package com.example.ccrr.myapplication.empty; import android.content.Context; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import com.example.ccrr.myapplication.R; import java.util.List; /** * Created by ccrr on 2019/4/8. */public class FruitAdapter extends ArrayAdapter< Fruit> {private int resourceId; public FruitAdapter(Context context,int textViewResourceId, List< Fruit> objects) { super(context, textViewResourceId, objects); resourceId=textViewResourceId; }@NonNull @Override public View getView(int position, View convertView, ViewGroup parent) {Fruit fruit = getItem(position); //获取当前项Fruit实例 //View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false); View view; if (convertView ==null){ view=LayoutInflater.from(getContext()).inflate(resourceId,parent,false); }else { view = convertView; }ImageView imageView = (ImageView) view.findViewById(R.id.image_view); TextView textView = (TextView) view.findViewById(R.id.text_view); imageView.setImageResource(fruit.getId()); textView.setText(fruit.getName()); return view; } }

 
修改上述的代码,重新运行!
这里再getView()中进行判断
如果为null,则使用LayoutInflater去加载布局
不为null,则直接对convertView进行重用
这就大大的提高了效率
 
现在的代码还能进行优化
此时不会再重复去加载布局
但是每次再geiVIew()方法中会调用View的findViewByID()方法来获取一次控件的实例
此时可以借助ViewHolder对这部分进行优化
package com.example.ccrr.myapplication.empty; import android.content.Context; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import com.example.ccrr.myapplication.R; import java.util.List; /** * Created by ccrr on 2019/4/8. */public class FruitAdapter extends ArrayAdapter< Fruit> {private int resourceId; public FruitAdapter(Context context,int textViewResourceId, List< Fruit> objects) { super(context, textViewResourceId, objects); resourceId=textViewResourceId; }@NonNull @Override public View getView(int position, View convertView, ViewGroup parent) {Fruit fruit = getItem(position); //获取当前项Fruit实例 ViewHolder viewHolder; View view; if (convertView ==null){ view=LayoutInflater.from(getContext()).inflate(resourceId,parent,false); viewHolder = new ViewHolder(); viewHolder.imageView = (ImageView) view.findViewById(R.id.image_view); viewHolder.textView = (TextView) view.findViewById(R.id.text_view); view.setTag(viewHolder); }else { view = convertView; viewHolder= (ViewHolder) view.getTag(); }viewHolder.imageView.setImageResource(fruit.getId()); viewHolder.textView.setText(fruit.getName()); return view; }class ViewHolder{ ImageView imageView; TextView textView; } }

 
新增一个内部类ViewHolder用于对控件进行实例缓存
同理再if中进行判断
用View的setTag()方法将其存入View中
再有缓存时使用getTag()方法取出
 
4、ListView的点击事件上述的ListView只是视觉效果,并没有点击的用途
此时实现其点击事件
public class MainActivity extends AppCompatActivity {private List< Fruit> fruitList = new ArrayList< > (); private void initFruit(){ for (int i =0; i< 6; i++){ Fruit apple = new Fruit("Apple1",R.drawable.qq); fruitList.add(apple); Fruit apple1 = new Fruit("Apple2",R.drawable.qq); fruitList.add(apple1); Fruit apple2 = new Fruit("Apple3",R.drawable.qq); fruitList.add(apple2); Fruit apple3 = new Fruit("Apple4",R.drawable.qq); fruitList.add(apple3); Fruit apple4 = new Fruit("Apple5",R.drawable.qq); fruitList.add(apple4); Fruit apple5 = new Fruit("Apple6",R.drawable.qq); fruitList.add(apple5); Fruit apple6 = new Fruit("Apple7",R.drawable.qq); fruitList.add(apple6); }}@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.firstlayout); ActionBar actionBar = getSupportActionBar(); if (actionBar != null){ actionBar.hide(); }initFruit(); FruitAdapter adapter = new FruitAdapter( MainActivity.this,R.layout.fruit_item,fruitList); ListView listView = (ListView) findViewById(R.id.list_view); listView.setAdapter(adapter); //点击事件 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView< ?> parent, View view, int position, long id) { Fruit fruit = fruitList.get(position); Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show(); } }); } }

这里使用setOnItemClickListener()方法为ListView注册一个监听器
当用户点击任何一个子项时
就会回调onItemClick()方法
这个方法中通过position参数判定用户点击是哪一个子项
然后获得响应的事件
此时是打印处结果
 
2Android-UI(自定义控件&amp;ListView)

文章图片

 

    推荐阅读