2、Android-UI(自定義控件&ListView)
2.4、系統控件不夠用創建自定義控件
控件的和布局的集成結構:
所有的控件都是間接或者直接集成View的
所有的布局都是直接或者間接繼承自ViewGroup的
View是Android種最基本的一種UI組件
可以再屏幕上進行創建任何布局或者各種事件
所以使用的各種控件其實就是再View的基礎上添加了特有的功能
ViewGroup是一個特殊的View
他可以包含很對的子View和ViewGroup,是一個用於放置控件和布局的容器
當系統提供的控件不滿足開發時,可以自己創建自定義的控件
1、引入布局
實現標題欄的代碼進行解析
新建一個tittle.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"> <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:用於指定控件再上下左右方向的便宜距離
效果:
再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>
同時還需要將系統自帶的標題欄給隱藏
@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時出現提示信息
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 = {"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之間就建立了聯系
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; } }
新建:
<?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="@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中即可進行顯示,
此時定制的頁面比較簡單,只要修改對應的文件內容就可以制作出各種復雜的界面。
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參數判定用戶點擊是哪一個子項
然後獲得響應的事件
此時是打印處結果
2、Android-UI(自定義控件&ListView)