ListView Project (8)











ListView project ေလးတစ္ခု အပ်င္းေျပ ေရးၾကည္႔ရေအာင္ဗ်ာ ... အပိုင္း ( 8 )
***********************

အပိုင္း (8) ေတာင္ တိုင္ခဲ႔ေလျပီ။ အရင္အပုိုင္းမွာ Dialog Box ကေန Add Button ကို ႏွိပ္ကာ နာမည္၊ ဖုန္း EditText က စာေတြကို ဖမ္းျပီး AIDEFighter object ထဲထည္႔က အဲ႔ဒီ object ေတြကို ListView မွာ တင္ျပႏိုင္ခဲ႔ပါျပီ။

ဆိုေတာ႔ ListView မွာ ရွိေနတဲ႔ ဒီ AIDEFighter object တစ္ခုဆီတိုင္းမွာ က်ေနာ္တို႔ ထည္႔လိုက္တဲ႔ data အသီးသီးကို ListView အတြက္ တာဝန္ယူရတဲ႔ FighterAdapter class က က်ေနာ္တို႔ @Override လုပ္ခဲ႔တဲ႔ getView(...) method အတြင္းကေန ထုတ္ယူျပီး သက္ဆိုင္ရာ TextView ေတြမွာ ျပလို႔ရပါျပီ။

ဒါေၾကာင့္ အရင္အပိုင္းတုန္းကလို အခု getView() method အတြင္းမွာ View ေတြကို id ခ်ိတ္ၾကရေအာင္။ (ss မွာလည္း ျပထားပါမယ္။)

ဆိုေတာ႔ က်ေနာ္တို႔ ListView ရဲ႕ row အတြက္ သံုးခဲ႔တဲ႔ Layout က list_row.xml ဆိုေတာ႔ အဲ႔ layout xml ဖိုင္ဆီသြားျပီးေတာ႔ id ေတြ သတ္မွတ္ေပးလိုင္ပါ။

ဘယ္ဖက္ရွိေနတဲ႔ TextView ကို
nameTv
ညာဖက္ေထာက္က TextView ကို
levelTv
x နဲ႔ Button ကို
deleteBtn
Call Button ကို id
callBtn

လို႔ အသီးသီး id ေပးလိုက္ပါ။

id ေပးျပီးသားေတြ ျဖစ္ေနရင္လည္း ဖ်က္ျပီး အခု id နာမည္ေတြပဲ ေပးလိုက္ပါ။

အရင္အပိုင္းတုန္းကအတိုင္း အခု View ေတြက rowLayout ထဲမွာ ရွာရမွာ ျဖစ္တဲ႔အတြက္

rowLayout(၏)findViewById(R.id.xxxx);

TextView nameTv = (TextView) rowLayout.findViewById(R.id.nameTv);
TextView levelTv = (TextView) rowLayout.findViewById(R.id.levelTv);

Button deleteBtn = (Button) rowLayout.findViewById(R.id.deleteBtn);
Button callBtn = (Button) rowLayout.findViewById(R.id.callBtn);


ျပီးရင္ deleteBtn နဲ႔ callBtn ကိုလည္း အရင္အပိုင္းကအတိုင္း onClickListener ခ်ိတ္လိုက္ပါ။

ဒါဆို TextView ေတြ Button ေတြကို code က သိသြားျပီ။

ဒီ FighterAdapter class ရဲ႕ getView() method က ListView မွာေပၚေနတဲ႔ item တစ္ခုခ်င္းစီ တစ္ခုခ်င္းစီတိုင္းကို ကိုယ္စားျပဳေနတာပါ။ ဒါေၾကာင့္ ဒီ method တြင္းကေန ListView မွာေပၚေနတဲ႔ item တစ္ခုခ်င္းစီရဲ႕ data ကို တစ္ေနရာတည္းက လွမ္းယူလို႔ရတယ္။

ဆိုေတာ႔ ေစာေစာက id ခ်ိတ္လိုက္တဲ႔ nameTv ဆိုတဲ႔ TextView။ သူ႕ကို ListView ထဲက item ရဲ႕ နာမည္ data ကို ယူျပီး ျပေပးမယ္ဆို။

က်ေနာ္တို႔ ဒီ FighterAdapter class ေရးတုန္းက class အတြင္းမွာ ယူသံုးဖို႔ ဖမ္းထားတဲ႔ List စာရင္းရွိတယ္။ mFighterList;

mFighterList ထဲမွာ ထည္႔ထားတာက AIDEFighter object ေလးေတြ။ ဒီ object တစ္ခုစီတိုင္းမွာ နာမည္၊ ဖုန္း၊ Level data အသီးသီး ဖန္တီးထည္႔တုန္းက ထည္႔ေပးထားလို႔ ပါလာျပီးသား။

အခု getView() method အတြင္းကေန AIDEFighter ေလးေတြပါတဲ႔ mFighterList ထဲကေန ListView မွာ ရွိေနတဲ႔ position ေနရာ ကေန AIDEFighter တစ္ခု လွမ္းယူမယ္ -

mFighterList.get(position)

ဒါဆို က်ေနာ္တို႔လက္ထဲ သက္ဆိုင္ရာ position က AIDEFighter object တစ္ခု ရလာျပီ။ ရလာတဲ႔ object ကေန နာမည္အတြက္ getName() ဆိုတဲ႔ က်ေနာ္တို႔ AIDEFighter class ေရးတုန္းက ေရးခဲ႔တဲ႔ getter method နဲ႔ နာမည္ကို လွမ္းယူမယ္။ က်ေနာ္တို႔ ဒီ getter method ေတြ ေရးတုန္းက return type ေတြကို String type နဲ႔ သတ္မွတ္ခဲ႔တာ သတိရဦးမွာပါ။ ဒါေၾကာင့္ အခု getName() getter method က ရလာမွာက နာမည္ကို String type နဲ႔ေပါ႔။ ဒီရလာတဲ႕ String ကို nameTv ေပၚမွာ တင္ျပေပးလိုက္ရံု။ ဒီလိုေပါ႔။

nameTv.setText(mFighterList.get(position).getName());

ေနာက္ levelTv အတြက္လည္း ထိုနည္း၄င္း -

levelTv.setText("Level " + mFighterList.get(position).getLevel());

သူ႕က်ေတာ႔ Dialog Box မွာ Level အတြက္ ထည္႔ခိုင္းတုန္း က 1/2/3 တစ္ခုခုပဲ ေရးထည္႔ပါလို႔ ေျပာထားခဲ႔လို႔ 1/2/3 ဂဏန္းတစ္ခုခုပဲ data ရလာမွာ။ ဒါကို ေရွ႕က "Level " ဒါမွ မဟုတ္ "Level is " ဆိုျပီး level TextView မွာ ကိုယ္ေပၚေစခ်င္တဲ႔ စာလံုးအတိုင္း ေပၚေအာင္ ထပ္ျဖည္႔ေရးေပးပါလို႔ ေရွ႔မွာ "Level " + ဆိုျပီး String ကို ဆက္ေပးထားတာပါ။

ဒီအဆင့္ထိ ျပီးသြားရင္ App ကို Build လုပ္ျပီး List ထဲ New Member ထည္႔ၾကည္႔ပါဦး။ ဒီတစ္ခါ ထည္႔လိုက္တဲ႔ data ေတြက ListView မွာ သက္ဆိုင္ရာ TextView ေတြမွာ လာေပၚေနျပီ ဆိုတာေတြ႕ရမွာပါ။

ကဲ ဒီ တစ္ခါ x Button ကို ႏွိပ္ရင္ List ထဲက သက္ဆိုင္ရာ position က item ကို ဖ်က္ပစ္ရမွာဆိုေတာ႔ deleteBtn ကို ခ်ိတ္ထားတဲ႔ onClickListener ထဲ ေရးမယ္။

သံုးရမယ္႔ စာရင္းက mFighterList ။ List ေတြမွာ item ေတြကို ဖ်က္ခ်င္ရင္ android က ေပးထားတဲ႔ remove() ဆိုတဲ႔ method ေပးထားတယ္။ သူ႔ထဲကို ကိုယ္ဖ်က္ခ်င္တဲ႔ item ရဲ႕ တည္ေနရာ index ကို ထည္႔ေပးလိုက္တာနဲ႔ သူ႔အလိုလို ဖ်က္ေပးသြားမွာ။ ဒီေနရာမွာ index ဆိုတာ position နဲ႔ အတူတူေပါ႔။ ဒါေၾကာင့္ အခု FighterAdapter class ရဲ႕ @Override method မွာ int position ဆိုျပီး ListView ထဲက item တစ္ခုခ်င္းစီရဲ႕ position ေနရာကို ယူသံုးႏိုင္ေအာင္ ေပးထားတာပါ။

mFighterList.remove(position);

ဒီေနရာမွာ position မွာ error လာျပပါမယ္။ အဲ႔ variable ကို final လုပ္ခိုင္းတာပါ။ error ေပၚကို ေထာက္ျပီး အေပၚဘားတန္းက အမွန္ျခစ္ေနရာကိုသြားျပီး Make final လုပ္ေပးလိုက္ပါ။

x (delete) Button ကို ႏွိပ္လိုက္တာနဲ႔ ဒီ code က အလုပ္လုပ္ျပီး item ကို ဖ်က္ပစ္ေပးမွာ။ ဒါဆို mFighterList စာရင္းက အသစ္ျဖစ္သြားျပီဆိုေတာ႔ ဒီ List စာရင္း အသစ္ကို အသစ္ျပန္ျပဖို႔ က်ေနာ္တို႔ MainActivity class က showMyList() method ကို ျပန္ method call/run လုပ္ဖို႔  လိုဦးမွာ။

ဒီတစ္ခါလည္း casting လုပ္ျပီး အလြယ္နည္းနဲ႔ အဲ႔ method ကိုလွမ္း call/run ပါမယ္။
((MainActivity)mContext).showMyList();

ဒီ code ေလးကို MySpecialDialog class ရဲ႕ Add Button onClick() အတြင္းမွာလည္း သံုးခဲ႔ဖူးပါတယ္။

အဲ႔ဆီမွာတုန္းက mContext ေနရာမွာ getActivity() နဲ႔ပါ။

getActivity() တို႔ အခု mContext ႏွစ္ခုစလံုးက "this" reference ကို ကိုယ္စားျပဳတာပါ။ ဒီ casting လုပ္တဲ႔အပိုင္းကို Java မွာ Upcasting နဲ႔ Downcasting ဆိုျပီး ေခါင္းစဥ္ခြဲေလးေတြ ရွိပါတယ္။ ရွာဖတ္ၾကည္႔ပါဦး။

ဒါဆို deleteBtn onClick အတြင္းမွာ ေရးရမယ္႔ code ႏွစ္ေၾကာင္း ျပီးသြားပါျပီ။ ေနာက္ ဖုန္းနံပါတ္အပိုင္းလည္း ပါေသးတယ္ဆိုေတာ႔ Call Button ႏွိပ္လိုက္ရင္ သက္ဆိုင္ရာ position က AIDEFighter object ဆီက ဖုန္းနံပါတ္ကို ယူျပီး ဖုန္းေခၚေပးဖို႔ method ေလး တစ္ခု ေရးရေအာင္။ နာမည္က callPhone ေပါ႔။ (ဖုန္းေခၚတဲ႔ permission ထည္႔ေပးဖို႔လည္း မေမ႔ပါနဲ႔ဦး။ ) အခု method ကို ဒီ class ထဲတင္ သံုးမွာမို႔ method scope ကို private modifier နဲ႔ ေရးလိုက္ပါ။

private void callPhone() {

}

ဖုန္းေခၚဖို႔ ဖုန္းနံပါတ္လိုမယ္။ ဆိုေတာ႔ input data အေနနဲ႔ ဖုန္းနံပါတ္ကို String type နဲ႔ ေတာင္းထားလိုက္ပါ။

private void callPhone(String phone) {

}

ကိုယ္က တစ္ခုခု လုပ္ခ်င္တယ္ဆိုရင္ ဘာလုပ္ခ်င္တာလဲ ဆိုတဲ႔ Intention/Intent ရည္ရြယ္ခ်က္ကို ရည္ညႊန္းျပီး android မွာ Intent ကို Google က ဖန္တီးေပးထားတာပါ။ ဖုန္းပဲ ေခၚခ်င္ေခၚခ်င္၊ share ပဲ လုပ္ခ်င္လုပ္ခ်င္ ကိုယ္လုပ္ခ်င္တဲ႔ Intent အတြက္ Intent တစ္ခုကို ဖန္တီးေပးရပါတယ္။

Intent mIntent = new Intent();

ကိုယ္ Intent ရဲ႕ လုပ္ေပးရမယ္႔ အလုပ္ action က ဖုန္းေခၚခ်င္တာဆို

mIntent.setAction(Intent.ACTION_CALL);

ျပီးေတာ႔ ဒီ action အတြက္ လိုအပ္မယ္႔ data အခု ဖုန္းေခၚမွာဆိုေတာ႔ အေပၚက input ေတာင္းယူထားတဲ႔ phone နံပါတ္ေပါ႔။

mIntent.setData(Uri.parse("tel:" + phone));

ေနာက္ဆံုးအေနနဲ႔ ဒီ Intent ကို တကယ္လုပ္ေပးပါေပါ႔။

mContext.startActivity(mIntent);

ဒီေနရာမွာ startActivity(.....) တို႔လို method ေတြက Activity ကို extends လုပ္ထားတဲ႔ (တစ္နည္း) "this" reference ရွိတဲ႔ class ေတြ (ဥပမာ MainActivity class) မွာသာ တိုက္ရိုက္ယူသံုးႏိုင္တာပါ။ မဟုတ္ရင္ ဒီ "this" reference ကို အရင္ရွာျပီးမွ အဲ႔ method ကို ေခၚသံုးႏိုင္မွာပါ။

ဒါေၾကာင့္ အခု က်ေနာ္တို႔ ေရးေနတဲ႔ FighterAdapter class မွာ constructor ကေန input အျဖစ္ MainActivity class ဆီက ေတာင္းယူလာတဲ႔ ဒီ "this" reference ရွိတဲ႔ mContext ကို အရင္ရွာျပီးမွ startActivity() method ကို ေခၚသံုးေပးရျခင္း ျဖစ္ပါတယ္။

mContext.startActivity(mIntent);

ဒါဆို အခု callPhone method အျပည္႔အစံုက ဒီလို ရလာပါမယ္။

private void callPhone(String phone) {

Intent mIntent = new Intent();
mIntent.setAction(Intent.ACTION_CALL);
mIntent.setData(Uri.parse("tel:" + phone));
mContext.startActivity(mIntent);

}

ဖုန္းေခၚေပးရမွာက call Button ႏွိပ္မွ ဆိုေတာ႔ အဲ႔ Button onClick အတြင္းကေန ဒီ method ဆီကို method call လုပ္ျပီး run ခိုင္းေပးလိုက္ပါ။

method call လုပ္ရင္ ဒီ method က input အျဖစ္ ေတာင္းထားတဲ႔ ဖုန္းနံပါတ္ String ကို ေစာေစာက AIDEFighter object က နာမည္ေတြ level String ေတြ ယူသလို လွမ္းယူျပီး ဒီ method ဆီကို ထည္႔ေပးလိုက္ေပါ႔။

ဒါဆို ဟိုအေပၚက call Button အတြက္ onClickListener တစ္ခုလံုးအတြက္ ေရးရမယ္႔ code က ဒီလို ျဖစ္ေနမွာေပါ႔။

callBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

callPhone(mFighterList.get(position).getPhone());

}
});

ဒါဆို ဒီအပိုင္းက အဆံုးသတ္ကေတာ႔မွာပါ။ ဒါေပမယ္႔ အခု project အတြက္ ဒီ FighterAdapter class ကို ဒါေနာက္ဆံုးျပင္ေရးတာ ျဖစ္ျပီး ေနာက္အပိုင္းေတြ လက္ဖ်ားနဲ႔ေတာင္ ထိေတာ႔မွာမဟုတ္တဲ႔အတြက္ က်ေနာ္တို႔ List row ေလးေတြကို animation ထည္႔ခဲ႔ရေအာင္လား။ ေရးဗ်ာ method ေနာက္တစ္ခု moveMyView ေပါ႔။

private void moveMyView()
{


}

Animation လုပ္ခ်င္တဲ႔ View ကို input အျဖစ္ေတာင္းယူထားမယ္ mView နာမည္နဲ႔

private void moveMyView(View mView) {


}

အလြယ္ဆံုး code သံုးေၾကာင္းနဲ႔ ေရးလို႔ရတဲ႔ TranslateAnimation တစ္ခု သံုးမယ္ေပါ႔။

TranslateAnimation instance တစ္ခု ဖန္တီးမယ္-

TranslateAnimation mAnimation = new TranslateAnimation(x, 0, 0, 0);

( အေပၚက code ထဲက x တန္ဖိုး declare လုပ္ မေၾကျငာရေသးဘူးဆိုေတာ႔ ဟိုးး အေပၚက class ထိပ္ဆံုးမွာ

int x = 600;

လို႔ ေၾကျငာေပးထားလိုက္ပါ။ )

အခ်ိန္ သတ္မွတ္မယ္ -

mAnimation.setDuration(800);

View ကို animate လုပ္မယ္

mView.setAnimation(mAnimation);

class ထိပ္က x တန္ဖိုးကို အေပါင္းတစ္လွည္႔ အႏုတ္တစ္လွည္႔ လဏၡာေျပာင္းေပးမယ္။

x = - x;

ဆိုေတာ႔ ဒီ method အျပည္႔အစံုက

private void moveMyView(View mView) {

TranslateAnimation mAnimation = new TranslateAnimation(x, 0, 0, 0);
mAnimation.setDuration(800);
mView.setAnimation(mAnimation);

x = - x;
}

ဒီ method ကို အေပၚက @Override method getView() ထဲက return statement မတိုင္ခင္ method call လုပ္ေပးလိုက္ပါ။ method call ထဲကို ကိုယ္႔  animation ထည္႔ခ်င္တဲ႔ View ျဖစ္တဲ႔ List ရဲ႕ row View ျဖစ္တဲ႔ rowLayout ကို pass (ထည္႔ေပး) လိုက္ရံုပါ။

moveMyView(rowLayout);

ေနာက္ဆံုး ေနာက္ဆံုးအေနနဲ႔ ဒီ project သေဘာတရားကေန ယူျပီး List စာရင္းေတြ အမ်ားၾကီး အမ်ားၾကီး ထည္႔ဖို႔ အခုလို custom Adapter class ေရးၾကမယ္႕သူေတြအတြက္ ListView ေလးတာေတြ ဘာေတြကို ေရွာင္ရွားႏိုင္ရန္ အခုလို နည္းနည္းေလး ျပင္ေရးလိုက္ပါ။

က်ေနာ္တို႔ FighterAdapter class ရဲ႕ getView() method တာဝန္ဟာ ListView မွာေပၚမယ္႔ item row တစ္ခုခ်င္းစီအတြက္ view ေတြကို ထုတ္ေပးဖို႔ တာဝန္ယူထားတာျဖစ္တဲ႔အတြက္ က်ေနာ္တို႔က ဒီ view ကို LayoutInflator နဲ႔ လွမ္းယူျပီး rowLayout View အေနနဲ႔ အစားထိုး ေပးေပးျပီး အသံုးျပဳခဲ႔တယ္။ အမွန္က က်ေနာ္တို႔ သံုးမယ္႔ layout ကို super class ဆီကို constructor နဲ႔ လွမ္းပို႔ျပီးသား။

super(context,R.layout.list_row, ....

ဒီ View ကို အခု getView() method အတြင္းက convertView အေနနဲ႔ super class က View ေျပာင္းခ်င္ရင္ ေျပာင္းလို႔ရေအာင္ ျပန္ခ်ေပးတယ္။ ဒီ View ကို က်ေနာ္တို႔က ယူသံုးေပးရမယ္။

View rowLayout = convertView;

ဒါေပမယ္႔ အဲ႔ဒီ convertView က null ျဖစ္ေနလား စစ္ေပးရမယ္။

if(rowLayout == null) {

null ျဖစ္ေနတယ္ဆိုမွ က်ေနာ္တို႔က LayoutInflator ကို သံုးျပီး View တစ္ခုကို ဖန္တီးအစားထိုးေပးလိုက္ရင္ LayoutInflator ကို ခဏခဏ ယူသံုးစရာ မလိုေတာ႔တဲ႕အတြက္ ListView က သြက္လာပါမယ္။ ဒါေၾကာင့္ အခု getView() method အတြင္း TextView ေတြကို id မခ်ိတ္ခင္ ဒီ convertView ကို ယူသံုးႏိုင္ေအာင္ အခုလို ျပင္ေရးလိုက္ပါ။

View rowLayout = convertView;

if(rowLayout == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowLayout = inflater.inflate(R.layout.list_row, parent, false);
}

ဘာျဖစ္လို႔ convertView ကို null စစ္ရပါသလဲ။ ဒီ convertView ဟာ ListView ထဲက item ေတြထဲက ဖုန္းမ်က္ႏွာျပင္ျမင္ကြင္းကေန ေပ်ာက္ကြယ္သြားတဲ႔ item View ေတြကို ယူယူျပီး ListView item ေတြအတြက္ View layout ကို အသစ္မလုပ္ပဲ reuse ျပန္ျပန္သံုးေပးတဲ႔ View တစ္ခုျဖစ္ပါတယ္။ ဆိုေတာ႔ item တစ္ခုဟာ ဖုန္းမ်က္ႏွာျပင္ျမင္ကြင္းကေန တစ္ဝက္တစ္ပ်က္ ေပၚမလို ေပ်ာက္မလိုျဖစ္ေနတဲ႔အေျခအေနမ်ိဳးမွာ android system က ဒီ item ရဲ႕ View ကို ယူျပီး reuse ျပန္သံုးႏိုင္တဲ႔ convertView အျဖစ္ျပန္ခ်ေပးဖို႔ ဆံုးျဖတ္ရ အခက္ေတြ႕ေနတဲ႔အတြက္ ဒီ convertView ဟာ ျပန္ခ်ေပးရင္ null မဟုတ္ပဲ ပါလာမယ္။ ျပန္ခ်မေပးရင္ ဗလာ null အေနနဲ႔ ျဖစ္ေနမယ္။ က်ေနာ္တို႔က ဒီ convertView ကို ယူသံုးခ်င္ေသးတာ ျဖစ္တဲ႔အတြက္ null စစ္ျပီး null ျဖစ္ေနမွ LayoutInflator ကို သံုးျပီး အေပၚကလို View တစ္ခု ဖန္တီးယူသံုးသြားျခင္း ျဖစ္ပါတယ္။ ဒါေၾကာင့္ အင္တာနက္ေပၚကေတြ႕ရတဲ႔ Adapter class ေတြမွာ ဒီ convertView ကို null စစ္တဲ႔ code ေလးေတြ ျမင္ဖူးၾကပါလိမ္႔မယ္။

if(convertView == null) {

// ...

}

ကဲ ဒီေလာက္ပါပဲ။ ဒီအပိုင္း ရၾကမယ္ ထင္ပါတယ္။ List item ေတြ အမ်ားၾကီးထည္႔ျပီး ListView ကို ကစားၾကည္႔ပါဦး။ မရွင္းတဲ႔ေနရာ comment ကေန ေမးႏိုင္ပါတယ္လို႔ ေျပာရင္း ...

အပိုင္း ( 9 ) ေမွ်ာ္ ... :)

-----------------------------------------
ကိုမ်ိဳး

AIDE Android Lessons And Project Group

မွ ကူးယူတင္ထားပါသည္

#mkk_ListView


Comments