14 Şubat 2018 Çarşamba

Android Dersleri 22 - Context activity mode (Context activity bar, RecyclerAdapter multiple selection)

2 - Context activity mode (Context activity bar, RecyclerAdapter multiple selection)
Bir view'e basılı tuttuğumuzda context activity mode açılır yani yukarıda geçici bir action bar gösterilir, bu actionbar'da seçili viewile ilgili yapılabilecek action'lar gösterilir. Yukarıdaki örneği incele. Gmail'de birden fazla mail seçmek için de bu kullanılır.
The above example illustrates a Contextual Action Mode. The Contextual Action Mode is provoked when the user long-clicks on a list view(or any other view such as textview,button,checkbox etc. etc.) item in order to select multiple items and perform certain actions such as deleting selected items. It's a great way to expose additional contextual actionable sub-tasks with Contextual Action Mode.
Contextual action mode'u şu url'de implement etmişler : http://www.androhub.com/android-contextual-action-mode-over-toolbar/ ancak acayip karışık implement etmişler o yüzden bu örneğe çok kafa yorma.
Example :
Daha düzgün ve anlaşılır bir örneği youtube'da with Contextual Action Mode isimli 8 videoluk seride PRABEESH R K implement etmiş : https://www.youtube.com/watch?v=HfmudzRaBak
https://github.com/DhjAsnani/ActionMode bu url'deki örneği yaptım. Bunun aynısını biraz değiştirip yapmışlar o da şu url'de : https://github.com/devlovepreet/RecyclerView-Contextual-Action-Mode
MainActivity.java : MainActivity class'ından incelemeye başlayalım.
is_in_action_mode isimli boolean variable tanımlarız, recyclerView'deki herhangi bir satıra uzun basılı tutunca action mode active yapıcaz action mode'un active olup olmadığını ise bu variable'a bakarak anlayacağız. Adapter class'ında bile action mode'da olup olmadığımızı kontrol etmek için MainActivity class'ındaki bu variable'ın değerine bakacağız.
RecyclerView'in satırlarında Contact object'lerin bilgilerini göstereceğiz. RecyclerView'de göstereceğimiz Contact object'lerin listesini arraylist isimli arraylist object'de tutacağız. Bu arraylist RecyclerView'in data source'u olacaktır yani. Peki bu arraylist'e ilgili Contact object'leri nerede koyuyoruz? MainActivity class'ının onCreate() method'unda :
String [] Name=getResources().getStringArray(R.array.names);
int i=0;
for(String NAME:Name)
{
   Contact contact = new Contact(NAME);
   arrayList.add(contact);
   i++;
}
Ayrıca MainActivity class'ında selectionList isimli bir arraylist tanımladık.RecyclerView'de bir satıra basılı tuttuğumuzda her satırda bir checklist gözükecektir. Bu checklist'lere tıkladıkça ilgili satırdaki Contact object, selectionList listeye eklenir.
onCreate() method'unda toolbar'ı set ederiz. RecyclerView object'i elde ederiz. Sonra toolbar'daki textView'i gizleriz :
TextView counterTextView = (TextView) findViewById(R.id.cnt_text);
counterTextView.setVisibility(View.GONE);

onCreate() method'unda en son RecyclerView ve adapter'ü birbirine bağlarız :
adapter = new RecyclerAdapter(arrayList,MainActivity.this);
recyclerView.setAdapter(adapter);
RecyclerView'de RecyclerViewHolder inner class'ının constructor'ında herbir satırdaki cardView element'e bir onLongClickListener register ederiz, böylece bir satıra tıklayınca mainActivity class'ındaki onClick() method'u çağırılır:
cardView = (CardView) itemview.findViewById(R.id.card_view);
cardView.setOnLongClickListener(mainActivity);

onLongClick() method'unda önce toolbar object'in menü xml'i resetlenir ve yeni menü.xml inflate edilir, action mode'un aktifleştiğini göstermek için is_in_action_mode variable true olarak set edilir. Sonra toolbar'daki textView visible yapılır. Sonra adapter'ün notifyDataSetChanged() method'u çağırılır peki neden? Çünkü RecyclerViewHolder inner class'ının constructor'ında  checkbox'a click listener register ederiz, checkbox visible yapılırsa adapter'ün bundan haberdar edilmesi için notifyDataSetChanged() method'u çağırılır.
@Override
public boolean onLongClick(View v) {
       toolbar.getMenu().clear();
       toolbar.inflateMenu(R.menu.menu_action_mode);
       is_in_action_mode = true;
       counterTextView.setVisibility(View.VISIBLE);
       adapter.notifyDataSetChanged();
       // home button on action bar
       getSupportActionBar().setDisplayHomeAsUpEnabled(true);
       return true;
}

RecyclerView'deki bir satıra basılı tutunca checkbox'lar visible yapılır. Checkbox'lara tıklarsak, MainActivity class'ındaki prepareselection() method'u çağırılır. Checkbox'ın tutulduğu RelativeLayout prepareSelection() method'una argument olarak verilir. Bu relativeLayout object relativeLayoutList isimli arraylist'e koyulur. Tıklanılan checkbox'In olduğu relativeLayout'un background rengini değiştireceğiz, ve bu relativelayout'u bir listeye koyacağız, delete butonuna tıkladıktan sonra daha önce rengini değiştirdiğimiz  relativelayout'ların rengini eski haline getireceğiz. Checkbox'a tıkladığımızda tick koymuşsak background rengini kırmızı yaparız, bu satırda gösterilen contact object'i selectionlist'e koyarız. counter'ı 1 arttırırız ve toolbar'da gösterilen textview'i güncelleriz :
  public void prepareselection(View view, int position, RelativeLayout relativeLayout)
{
   relativeLayoutList.add(relativeLayout);
   //change view to checkbox
   if(((CheckBox)view).isChecked())
   {
       relativeLayout.setBackgroundResource(R.color.myred); /** Change background color of the selected items in list view  **/
       selectionList.add(arrayList.get(position));
       counter++;
       updateCnt(counter);
   }
   else {
       relativeLayout.setBackgroundResource(R.color.forcard); /** Change background color of the selected items in list view  **/
       selectionList.remove(arrayList.get(position));
       counter--;
       updateCnt(counter);
   }
}

Toolbar'daki delete butonuna tıklarsak ne olacağını ise onOptionsItemSelected() method'unda tanımlarız, burada RecyclerAdapter class'ının updateAdapter() method'unı çağırırız bu method'a rgument olarak checkbox'da tick koyulan satırlardaki Contact object'leri yani selectionList'i veririz, sonra clearActionM() method'unu çağırırız :
RecyclerAdapter recyclerAdapter = (RecyclerAdapter) adapter;
recyclerAdapter.updateAdapter(selectionList);
clearActionM();

updateAdapter() method'unda selectionList'deki object'ler yani tickbox'ına tick koyulan Contact object'ler RecyclerView'in datasource'undan yani adapter_list'den silinir sonra adapter notify edilir :
for(Contact contact:list)
{
           adapter_list.remove(contact);
}
notifyDataSetChanged();
delete butonuna tıkladığımızda en son çağırılan clearActionM() method'u ne işe yarıyor? Bu method çağırılınca is_in_action_mode variable false olarak set edilerek action mode'dan çıkılması sağlanır. Sonra Toolbar'da gösterilen menü xml değiştirilir. Geri tuşu kaldırılır. Toolbar'daki textView' yeni bir text set ederiz ve textview'i görünmez yaparız. Counter'ı sıfırlarız. SelectionList'i resetleriz :
is_in_action_mode = false;
toolbar.getMenu().clear();
toolbar.inflateMenu(R.menu.menu_activity_main);
//remove home button
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
counterTextView.setVisibility(View.GONE);
counterTextView.setText("0 item selected");
counter = 0;
selectionList.clear();

MainActivity class'ının tamamı :
public class MainActivity extends AppCompatActivity implements View.OnLongClickListener{
   boolean is_in_action_mode = false;
   TextView counterTextView;
   RecyclerView recyclerView;
   RecyclerView.LayoutManager layoutManager;
   RecyclerView.Adapter adapter;
   Toolbar toolbar;
   ArrayList<Contact> arrayList = new ArrayList<Contact>();
   ArrayList<Contact> selectionList = new ArrayList<Contact>();
   int counter = 0;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       toolbar = (Toolbar) findViewById(R.id.toolbar);
       setSupportActionBar(toolbar);
       // initialise recycler view
       recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
       layoutManager = new LinearLayoutManager(this);
       recyclerView.setLayoutManager(layoutManager);
       recyclerView.setHasFixedSize(true);
       counterTextView = (TextView) findViewById(R.id.cnt_text);
       counterTextView.setVisibility(View.GONE);
       String [] Name=getResources().getStringArray(R.array.names);
       int i=0;
       for(String NAME:Name)
       {
           Contact contact = new Contact(NAME);
           arrayList.add(contact);
           i++;
       }
       adapter = new RecyclerAdapter(arrayList,MainActivity.this);
       recyclerView.setAdapter(adapter);
   }
   //adding menu to toolbar

   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
       getMenuInflater().inflate(R.menu.menu_activity_main, menu);
       return true;
   }

   @Override
   public boolean onLongClick(View v) {
       toolbar.getMenu().clear();
       toolbar.inflateMenu(R.menu.menu_action_mode);
       is_in_action_mode = true;
       counterTextView.setVisibility(View.VISIBLE);
       adapter.notifyDataSetChanged();
       // home button on action bar
       getSupportActionBar().setDisplayHomeAsUpEnabled(true);
       return true;
   }

  public void prepareselection(View view, int position, RelativeLayout relativeLayout)
{
   relativeLayoutList.add(relativeLayout);
   //change view to checkbox
   if(((CheckBox)view).isChecked())
   {
       relativeLayout.setBackgroundResource(R.color.myred); /** Change background color of the selected items in list view  **/
       selectionList.add(arrayList.get(position));
       counter++;
       updateCnt(counter);
   }
   else {
       relativeLayout.setBackgroundResource(R.color.forcard); /** Change background color of the selected items in list view  **/
       selectionList.remove(arrayList.get(position));
       counter--;
       updateCnt(counter);
   }
}

   public void updateCnt(int counter)
   {
       if(counter==0)
       {
           counterTextView.setText("0 item selected");
       }
       else {
           counterTextView.setText(counter + " item selected");
       }
   }

   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
       if(item.getItemId()==R.id.it_delete)
       {
           RecyclerAdapter recyclerAdapter = (RecyclerAdapter) adapter;
           recyclerAdapter.updateAdapter(selectionList);
           clearActionM();
       }
       else if(item.getItemId() == android.R.id.home)
       {
           clearActionM();
           adapter.notifyDataSetChanged();
       }
       return true;
   }

   public void clearActionM()
   {
       is_in_action_mode = false;
       toolbar.getMenu().clear();
       toolbar.inflateMenu(R.menu.menu_activity_main);
       //remove home button
       getSupportActionBar().setDisplayHomeAsUpEnabled(false);
       counterTextView.setVisibility(View.GONE);
       counterTextView.setText("0 item selected");
       counter = 0;
       selectionList.clear();
   }

   @Override
   public void onBackPressed() {
       if(is_in_action_mode)
       {
           clearActionM();
           adapter.notifyDataSetChanged();
       }
       else {
           super.onBackPressed();
       }

   }
}

RecyclerAdapter.java
RecyclerAdapter class'ının constructor'ı argument olarak datasource olan ArrayList object ve context object alır.
RecyclerView'de herbir satırın layout'unun nasıl görüneceğini onCreateViewHolder() method'unda tanımlarız.Yine bu method'da bir RecyclerViewHolder object yaratıp return ederiz.
onBindViewHolder() method'unda, RecyclerView'in satırlarındaki TextView'lere yani RecyclerViewHolder'daki TextView'lere data source'daki text'ler set edilir. mainactivity'nin is_in_action_mode variable'ına bakılarak action modda olup olmadığı anlaşılır buna göre checkbox gösterilir veya gizlenir.
RecyclerViewHolder class'ında, RecyclerView layout'unun satırındaki view element'ler elde edilir. cardView'e LongClickListener register edilir cardView'e basılı tutulursak MainActivity class'ındaki onLongClick() method'u çağırılır. Checkbox'a click listener register edilir, checkbox'a tıklanırsa MainActivity class'ının prepareSelection() method'u çağırılır, prepareSelection() method'una 2.argument olarak tıklanılan satırın pozisyonu verilir.
RecyclerAdapter updateAdapter method'u ise, delete butonuna tıklandığında MainActivity class'ının onOptionsItemSelected() method'u içerisinden çağırılır.
RecyclerAdapter class'ının tamamı :

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder> {

   ArrayList<Contact> adapter_list = new ArrayList<Contact>();
   MainActivity mainActivity;
   Context ctx;
   public RecyclerAdapter(ArrayList<Contact> adapter_list,Context ctx){
       this.adapter_list = adapter_list;
       this.ctx = ctx;
       mainActivity = (MainActivity) ctx;

   }
   @Override
   public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view_layout,parent,false);
       RecyclerViewHolder recyclerViewHolder= new RecyclerViewHolder(view,mainActivity);
       return recyclerViewHolder;
   }

   @Override
   public void onBindViewHolder(RecyclerViewHolder holder, int position) {
       holder.textView.setText(adapter_list.get(position).getName());
       if(!mainActivity.is_in_action_mode)
       {
           holder.checkBox.setVisibility(View.GONE);
       }
       else
       {
           holder.checkBox.setVisibility(View.VISIBLE);
           holder.checkBox.setChecked(false);
       }
   }

   @Override
   public int getItemCount() {
       return adapter_list.size();
   }

   public static class RecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

       TextView textView;
       CheckBox checkBox;
       MainActivity mainActivity;
       // cardview for long click
       CardView cardView;
       public RecyclerViewHolder(View itemview, MainActivity mainActivity)
       {
           // mainActivity is used for handling the click events of the checkboxes
           super(itemview);
           textView = (TextView) itemview.findViewById(R.id.textView);
           checkBox= (CheckBox) itemview.findViewById(R.id.checkBox);
relativeLayout = (RelativeLayout)itemview.findViewById(R.id.relativelayout);
           this.mainActivity = mainActivity;
           cardView = (CardView) itemview.findViewById(R.id.card_view);
           cardView.setOnLongClickListener(mainActivity);
           checkBox.setOnClickListener((View.OnClickListener) this);
       }

       @Override
       public void onClick(View v) {
      mainActivity.prepareselection(v,getAdapterPosition(),relativeLayout);
       }
   }

   public void updateAdapter(ArrayList<Contact> list)
   {
       for(Contact contact:list)
       {
           adapter_list.remove(contact);
       }
       notifyDataSetChanged();
   }
}

activity_main.xml : MainActivity'nin layout'u aşağıda tanımlanmıştır.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context=".MainActivity">

   <include layout="@layout/toolbar_layout"></include>
   <android.support.v7.widget.RecyclerView
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:id="@+id/recyclerview"
       android:layout_marginTop="15dp"
       ></android.support.v7.widget.RecyclerView>
</LinearLayout>

toolbar_layout.xml : MainActivity'nin layout'unda gösterilecek olan Toolbar aşağıda tanımlanmıştır.
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:id="@+id/toolbar"
   android:background="@color/colorPrimary"
   android:minHeight="?attr/actionBarSize"
   app:theme="@style/Base.ThemeOverlay.AppCompat.Dark.ActionBar">

   <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content">

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:id="@+id/cnt_text"
           android:text="0 item selected"
           android:textColor="#fff"
           android:textSize="20dp"
           android:textStyle="italic|bold"
           />
   </RelativeLayout>
</android.support.v7.widget.Toolbar>

card_view_layout.xml : RecyclerView'deki herbir satırın root layout'u LinearLayout'dur, bunun içerisinde CardView element'dir.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:orientation="vertical">
   <android.support.v7.widget.CardView
       xmlns:card_view = "http://schemas.android.com/apk/res-auto"
       android:layout_width="match_parent"
       android:layout_height="65dp"
       android:id="@+id/card_view"
       android:layout_margin="10dp"
       android:padding="10dp"
       card_view:cardCornerRadius="1dp"
       card_view:cardElevation="20dp"
       >
       <RelativeLayout
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:padding="15dp"
           android:background="@color/forcard">
           <TextView
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="TextView"
               android:textColor="#fff"
               android:textSize="25dp"
               android:textStyle="bold|italic"
               android:gravity="center_vertical"
               android:id="@+id/textView" />

           <CheckBox
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:id="@+id/checkBox"
               android:layout_alignParentBottom="true"
               android:layout_alignParentRight="true"
               android:layout_alignParentEnd="true"
               android:layout_marginTop="15dp"
               />
       </RelativeLayout>
   </android.support.v7.widget.CardView>

</LinearLayout>

menu_action_mode.xml : Action modda toolbar'ın menü xml'i bu olacaktır, delete buton gösterilecektir :
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">

   <item
       android:id="@+id/it_delete"
       android:icon="@drawable/ic_action_delete"
       android:title="Delete"
       app:showAsAction="always"></item>

</menu>

menu_activity_main.xml : Action modda değilken toolbar'ın menü xml'i bu olacaktır, search ve share buton gösterilecektir ancak bu butonların functionality'leri yoktur :
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">

   <item android:id="@+id/it_search"
       android:title="Search"
       android:icon="@drawable/ic_action_search"
       app:showAsAction="always"></item>
   <item android:id="@+id/it_share"
       android:title="Share"
       android:icon="@drawable/ic_action_share"
       app:showAsAction="always"></item>
</menu>

ListView artık çok demode ve düşük performanslı olduğu için ListView için Contextual Action Bar'ı incelemedik ve öğrenmedik.

Hiç yorum yok:

Yorum Gönder