Adobe Apps (Custom ListView - Version 2)

Create New Project

There are times when you want to show a collection of related data (e.g., books, movies) in an easy-to-read format.  A ListView is a great candidate for this this of task. A ListView will create a scrollable list of items that can be displayed.

In this tutorial, we will create an app that will use a Custom Adapter for the ListView:

  1. Open Android Studio and from the Quick Start panel, select Start A New Android Studio Project.
  2. In the New Project dialog box that appears, type Adobe Custom ListView App V2 for the Application name.
  3. Type example.com as the Company Domain and accept the other defaults and then click the Next button.
  4. In the Target Android Devices dialog box that appears, select only the Phone and Tablet checkbox, its default Minimum SDK and then click the Next button.
  5. In the Add an activity to Mobile dialog box that appears, select the Empty Activity option and then click the Next button.
  6. In the Customize the activity dialog box that appears, accept defaults and then click the Finish button.

As in many objects that are created, we will:

  1. Create objects (with elements and styles)
  2. Give objects names (with ids and resource strings)
  3. Tell objects to do something (with Java code)
    Even within the Java code, you still do the same thing:
    1. Create objects
    2. Give objects names
    3. Tell objects to do something
      NOTE: While you can do most of these items in any combination, we will do it in the order above to start with the simple concepts (creating and naming objects) and then move to the more advanced concept (adding code).

Create Object with Element

In this third version of the Adobe Apps, we will drag-and-drop a ListView component onto the screen and then use an Adapter and an array to populate a CUSTOM ListView.

  1. In the activity_main.xml file that opens, in Design view, right-click on the Hello World text and select Delete from the menu.
  2. From the Palette panel, drag-and-drop a ListView component and center it within the screen.
  3. (Optional) In the Component panel, select the RelativeLayout compound and then in the Properites list , delete all four padding properties so that the ListView takes up the entire screen.

    android:paddingBottom="@dimen/activity_vertical_margin" 
    android:paddingLeft="@dimen/activity_horizontal_margin" 
    android:paddingRight="@dimen/activity_horizontal_margin" 
    android:paddingTop="@dimen/activity_vertical_margin"

  4. CHECK POINT: In the preview panel, you should see the default ListView with items and subitems that are generated by the framework. If you were to preview it in an emulator, you would not see the ListView.



  5. (Optional) In the Text View, add a background property to the Relative Layout element to a color that goes well with black text:

    android:background="#5151fe">
  6. (Optional) In the ListView component, you can add a divider color and divider height properties if you want to:

    <ListView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/ListView"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true" android:divider="#cccccc"
    android:dividerHeight="1dp"
    />

  7. CHECK POINT: In the Preview panel, you should see the optional background color and divider lines with a height of one pixel.

Give Object Name with ID

While you could have done this at the SAME time you created the ListView component above, we wanted to do it in a systematic approach. However, in real production, it would be best to create the components and give them names at the same time they are created.
IMPORTANT CONCEPT TO REMEMBER: It is important to name every component that will be used by Java code with an id. The id is used to store a reference of the element in a Java object as you will see later.

  1. In Text View, give the ListView component a built-in id property of @android:id/list.
    NOTE: You MUST use this id when using the ListActivity class:

    <ListView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true" android:divider="#cccccc"
    android:dividerHeight="1dp"
    android:id="@android:id/list" />

Tell Object To Do Something with Code

  1. Right-click on the app and select New > Java Class.
  2. In the Choose Destination Directory dialog box that appears and select ...\app\src\java and then click the OK button:
    CAUTION: Ensure you choose the main\java folder and not any of the other folders.



  3. In the Create New Class dialog box that appears, enter a class Name of Book and then click the OK button:



  4. In the Book.java file that appears, enter two public members to the Book class and then add a space below them:

    public class Book {
    public String title;
    public String author;

    }

  5. Right-click and select Generate... from the menu or press ALT+INSERT and select Constructor from the menu.
  6. In the Choose Fields to Initialize by Constructor dialog box that appears, select both fields (title and author) and then press the OK button.



  7. CHECK POINT: You should see that a constructor is created in the code with the SAME name as the class. It will accept the two parameters (title and author) that are passed to it. Also, add the package at the top of the code (package com.example.adobecustomlistviewappv2;):

    package com.example.adobecustomlistviewappv2;
    public class Book {
    public String title;
    public String author;
    public Book(String title, String author) {
    this.title = title;
    this.author = author;
    }
    }

  8. Enter a line after the constructor and then right-click and select Generate... again from the menu or press ALT+INSERT and select Getter and Setter from the menu.
  9. In the Select Fields to Generate Getters and Setters dialog that appears, select both fields (title and author) and then press the OK button.



  10. CHECK POINT: You should see that the getters and setters were created in the code based on the two fields (title and author)

    package com.example.adobecustomlistviewappv2;
    
    public class Books {
    public String title;
    public String author;

    public Books(String title, String author) {
    this.title = title;
    this.author = author;
    }

    public String getTitle() {
    return title;
    }

    public void setTitle(String title) {
    this.title = title;
    }

    public String getAuthor() {
    return author;
    }

    public void setAuthor(String author) {
    this.author = author;
    }
    }

  11. Right-click on the app and select New > Java Class again.
  12. In the Choose Destination Directory dialog box that appears and select ...\app\src\java and then click the OK button.
  13. In the Create New Class dialog box that appears, enter a class Name of BookStore and then click the OK button.
  14. In the BookStore.java file that appears, enter the following highlighted code:
    NOTE: In a real app, this data come be retrieved from a database or a web service.

    package com.example.adobecustomlistviewappv2;

    import java.util.ArrayList;

    public class BookStore {

    public static List<Book> getBooks(){
    List<Book> books = new ArrayList<Book>();

    books.add(new Book("Book1","BookSubTitle1"));
    books.add(new Book("Book2","BookSubTitle2"));
    books.add(new Book("Book3","BookSubTitle3"));
    books.add(new Book("Book4","BookSubTitle4"));
    books.add(new Book("Book5","BookSubTitle5"));
    books.add(new Book("Book6","BookSubTitle6"));
    books.add(new Book("Book7","BookSubTitle7"));
    books.add(new Book("Book8","BookSubTitle8"));
    books.add(new Book("Book9","BookSubTitle9"));
    books.add(new Book("Book10","BookSubTitle10"));
    books.add(new Book("Book11","BookSubTitle11"));
    books.add(new Book("Book12","BookSubTitle12"));
    books.add(new Book("Book13","BookSubTitle13"));
    books.add(new Book("Book14","BookSubTitle14"));
    books.add(new Book("Book15","BookSubTitle15"));

    return books;
    }
    }

  15. Right-click on the layout folder and select New > Layout > Layout resource file.
  16. In the New Resource File dialog box that appears, enter a layout File name of book_item and then click the OK button:



  17. In the Design View, drag-and-drop a Large and Medium Text View to the top of the screen and give them an id of title and author, respectively. Then, select LinearLayout in the Component Tree and change the height property to wrap_content.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="Large Text"
    android:id="@+id/title" />

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:text="Medium Text"
    android:id="@+id/author" />
    </LinearLayout>

  18. In the MainActivity.java file BELOW theonCreate code block, add the following highlighted code to create a custom adapter:
    NOTE: You can create a custom adapter by extending the list adapter or the array adapter. We will use an array adapter by creating an inner class (a class in a class).

    public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    }
    class BookAdapter extends ArrayAdapter<Book>{

    }
    }
    }

  19. Press ALT+ENTER and select Create constructor matching super and then modify the code to read:

    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    }
    class BookAdapter extends ArrayAdapter<com.example.adobecustomlistviewappv2.Book>{

    public BookAdapter(Context context, int textViewResourceId, List<Book> books) {
    super(context,textViewResourceId, books);
    }
    }
    }
    }

  20. Create an inflater in the BookAdapter class:

    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    }
    class BookAdapter extends ArrayAdapter<com.example.adobecustomlistviewappv2.Book>{
    private LayoutInflater layoutInflater;
    public BookAdapter(Context context, int textViewResourceId, List<com.example.adobecustomlistviewappv2.Book> books) {
    super(context,textViewResourceId, books); layoutInflater = LayoutInflater.from(context);
    }
    }
    }
    }

  21. Below the closing currly brace on the BookAdapter, right-click and select Generate... from the menu.
  22. In the Select Methods to Override/Implement dialog box that appears, select the following highlighted getView method and then click the OK button.



  23. CHECK POINT: You should see the following highlighted code. Modify the code to read:

    public BookAdapter(Context context, int textViewResourceId, List<com.example.adobecustomlistviewappv2.Book> books) {
    super(context,textViewResourceId, books);
    layoutInflater = LayoutInflater.from(context);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    return super.getView(position, convertView, parent);
    }

  24. In the getView method, add the following highlighted code:

    public View getView(int position, View convertView, ViewGroup parent) {
    View view = layoutInflater.inflate(R.layout.book_item, null);
    Book book = getItem(position);
    // Get book properties and assigned them to the view object that is inflated
    // The findViewById is called on the view instance because the title and author TextViews are INSIDE of the layout View object
    TextView title = (TextView) view.findViewById(R.id.title);
    TextView author = (TextView) view.findViewById(R.id.author);

    title.setText(book.getTitle());
    author.setText(book.getAuthor());

    return view;
    // return super.getView(position, convertView, parent);
    }

  25. CHECK POINT: Run the app in an emulator. You should see the items displayed in the Listview. Scroll the page to see additional items.

    [ADD SCREEN SHOT HERE LATER]

(OPTIONAL) Add A Holder Class

If you have a small list of items (one or two dozen) then you can skip these steps. However, if you have a LARGE list of items, then the app may displays sluggish. If you examine the logcat WHILE scrolling the list, you will see:

A Choreographer is a component that is responsive for coordinating input events and screen drawing. It is responsible for keeping the input events in sync with the UI. Like a video if it cannot keep up the sync, it will “drop frames” to compensate. There are two things that can cause an app to run sluggish when you attempt to scroll a list view forward and backward:

The Garbage Collector monitors views that are visible. If you are scrolling up or down, once a view is not visible it no longer has a reference and can be “pick up” by the garbage collector. Moreover, new views will have to be created as you scroll up or down.

Hence, it is not efficient to create a lot of views.  To solve this problem, views can be RECYCLED.  The goal is to create an initial “set” of views that can be seen on the first screen and then recycle those views as needed. For example, when one view is moved OFF of the screen because the list was scrolled, another existing view can be recycled to show a view that is going to be moved ONTO the screen.

Adapters come with a recycler called convertView that can be used to speed up an app and make is more efficient by modifying the custom adapter code to:

Currently, the convertView parameter is not being used in the getView() method.

  1. Comment out or delete the current view and replace it with convertView parameter:

    // View view = layoutInflater.inflate(R.layout.book_item, null);
    View view = convertView;

  2. Write the following highlighted code below the current code to check if the view is null and if so inflate the view; otherwise use the same view.
    NOTE: This resolve the problem with creating unnecessary views:

    View view = convertView;
    Book book = getItem(position);
    if (view == null) {
    view = layoutInflater.inflate(R.layout.book_item, null);
    }

To avoid using the findViewById more than needed, we will use a Holder class which “holds” a reference to the views (e.g., title and author) that we are interested in.

  1. Below the BookAdapter class closing curly brace OR before the last curly brace, create a new Holder class:

    static class Holder {
    public TextView title;
    public TextView author;
    }
    }

  2. Below the two member fields (title and author) create a constructor by right-clicking and selecting Generate… from the menu and then selecting Constructor from the list, select both members and then clicking the OK button.
  3. CHECK POINT: You should see the constructor was added to the code based on the two member fields:

    static class Holder {
    public TextView title;
    public TextView author;
    public Holder(TextView title, TextView author) {
    this.title = title;
    this author = author;

    }

  4. In the getView() method, add the Holder variable, move the two TextView lines into the if code block AND add a new Holder class within the if statement and add an else statement as highlighted below:

    public View getView(int position, View convertView, ViewGroup parent) {
    View view = convertView; Book book = getItems(position);
    Holder holder = null;
    if (view == null) {
    view =  layoutInflater.inflate(R.layout.book_item, null);
    TextView title = (TextView) view.findViewById(R.id.title);
    TextView author = (TextView) view.findViewById(R.id.author);
    // Create a new holder object
    holder = new Holder(title, author);
    view.setTag(holder);
    }
    else {
    // Get holder from inflated view
    holder = (Holder) view.getTag();
    }

  5. Then, set the properties from the holder instead of directly from components:

    holder.title.setText(book.getTitle());
    holder.author.setText(book.getAuthor());

  6. CHECK POINT: Run the app in an emulator to ensure your code is correct. However, you may not see a different in the emulator so you will have to test this on a real device.

[ADD SCREEN SHOT HERE...]

 

 

 

 

 

 

 

 

 

 

 




Inflate Custom ListView With Java

  1. In the MainActivity.java file, comment out or delete the adapter that was created earlier.
    NOTE: In the upcoming steps, we will create a custom adapter that will be used.

    // ArrayAdapter<String> appsAdapter = new ArrayAdapter<String>(getApplicationContext(),
    // android.R.layout.simple_list_item_1,android.R.id.text1, appsStringArray);

  2. Right-click on the java folder and select File > New > Java Class from the menu or from the package folder (e.g., com.example.adobeapps) and in the Choose Destination Directory dialog box that appears, choose the directory ...\app\src\main\java and then click the OK button.



  3. In the Create New Class dialog box that appears in the Name text field, enter CustomListViewAdapter and then click the OK button.



  4. In the CustomListViewAdapter.java file that opens, type the following highlighted code:

    public class CustomListViewAdapter extends BaseAdapter {
    }

  5. Click inside of the class and press ALT+ENTER and then select Implement methods (of the parent BaseAdapter) from the menu.
  6. In the Select Methods to Implement dialog box that appears, select ALL of the methods, ensure @Override checkbox is selected and then click the OK button.



  7. CHECK POINT: You should see the the four methods of the BaseAdapter were added to the code AUTOMATICALLY:

    public class CustomListViewAdapter extends BaseAdapter {
    @Override
    public int getCount() {
    return 0;
    }

    @Override
    public Object getItem(int position) {
    return null;
    }

    @Override
    public long getItemId(int position) {
    return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    return null;
    }
    }

  8. Write or modify the following highlighted code of this class:

    public class CustomListViewAdapter extends BaseAdapter {

    private Context mContext;
    private ArrayList<HashMap<String,String>> topics;
    private static LayoutInflater inflater = null;


    public CustomListViewAdapter(Context context, ArrayList<HashMap<String, String>> data){
    mContext = context;
    topics = data;
    inflater = (LayoutInflater)context.getSystemService(context.LAYOUT_INFLATER_SERVICE);

    }
    @Override
    public int getCount() {
    return topics.size();
    }
    @Override
    public Object getItem(int position) {
    return position;
    }
    @Override
    public long getItemId(int position) {
    return position;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    View view = convertView;
    if (convertView == null){
    view = inflater.inflate(R.layout.custom_listview_row, null);
    }

    TextView title = (TextView) view.findViewById(R.id.title);
    TextView sub_title = (TextView) view.findViewById(R.id.sub_title);
    ImageView image = (ImageView) view.findViewById(R.id.list_icon);

    HashMap<String, String> mTopic = new HashMap<>();

    mTopic = topics.get(position);

    title.setText(mTopic.get("title"));
    sub_title.setText(mTopic.get("sub_title"));

    return view;
    }
    }

  9. Write the following code in the MainActivity.java file:

    public class MainActivity extends AppCompatActivity {

    private ListView myListView;
    private CustomListViewAdapter customListViewAdapter;

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

    myListView = (ListView) findViewById(R.id.appsListView);

    final ArrayList<HashMap<String,String>> titleList = new ArrayList<>();
    final String[] tittleArray = getResources().getStringArray(R.array.adobe_apps);
    final String[] subtitleArray = getResources().getStringArray(R.array.adobe_apps_subtitle);


    for (int i=0; i<subtitleArray.length; i++){
    HashMap<String,String> data = new HashMap<>();
    data.put("title", tittleArray[i].toString());
    data.put("sub_title",subtitleArray[i].toString());
    titleList.add(data);

    }
    // Create new adapter and assign it to the ListView
    customListViewAdapter = new CustomListViewAdapter(getApplicationContext(),titleList);
    myListView.setAdapter(customListViewAdapter);
    // Add Event Listener

    myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    String myItem = tittleArray[position];
    Toast.makeText(getApplicationContext(), myItem, Toast.LENGTH_SHORT).show();
    }

    });
    }
    }

  10. CHECK POINT: Run the app in an emulator. You should see the custom view layout inflated in the app. If you click on a list item, you should see a Toast message with the name of the app displayed. Also, notice that the icon is the same for each list item. This will be resolved in the upcoming steps:
  11. Copy and paste the thirteen app icons into the drawable folder.
  12. Add the following highlighted code in the getView() method:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    View view = convertView;
    if (convertView == null){
    view = inflater.inflate(R.layout.custom_listview_row, null);
    }

    TextView title = (TextView) view.findViewById(R.id.title);
    TextView sub_title = (TextView) view.findViewById(R.id.sub_title);
    ImageView image = (ImageView) view.findViewById(R.id.list_icon);

    HashMap<String, String> mTopic = new HashMap<>();

    mTopic = topics.get(position);

    title.setText(mTopic.get("title"));
    sub_title.setText(mTopic.get("sub_title"));

    if(position==0) {
    image.setImageResource(R.drawable.dreamweaver);
    }
    if(position==1) {
    image.setImageResource(R.drawable.photoshop);
    }
    if(position==2) {
    image.setImageResource(R.drawable.illustrator);
    }
    if(position==3) {
    image.setImageResource(R.drawable.indesign);
    }
    if(position==4) {
    image.setImageResource(R.drawable.animate);
    }
    if(position==5) {
    image.setImageResource(R.drawable.after_effects);
    }
    if(position==6) {
    image.setImageResource(R.drawable.premiere_pro);
    }
    if(position==7) {
    image.setImageResource(R.drawable.lightroom);
    }
    if(position==8) {
    image.setImageResource(R.drawable.muse);
    }
    if(position==9) {
    image.setImageResource(R.drawable.fuse);
    }
    if(position==10) {
    image.setImageResource(R.drawable.acrobat_pro_dc);
    }
    if(position==11) {
    image.setImageResource(R.drawable.audition);
    }
    if(position==12) {
    image.setImageResource(R.drawable.phonegap);
    }

    return view;
    }
    }

  13. CHECK POINT: Run the app in an emulator. This time you should see a different icon for each list item.


(Optional) Add Custom Selector

It is important to note how these components will be knitted together. The listview state xml files are nested inside of the selector xml file which is inside of the row xml which is pulled inside (inflated) in the java file (e.g., CustomListViewAdapter.java):


Not only do you have to create a custom layout, you have to create a custom adaptor as well. In order to show the custom ListView, we will have to inflate it with Java.

  1. Right-click on the drawable folder and select New > Drawable Resource File.
  2. In the Drawable Resource File dialog box that appears, in the File name text field enter custom_listview_normal, accept the other defaults and then click the OK button.
  3. In the custom_listview_normal file that opens, replace the text in the first <selector> element with <shape>.
    NOTE: Notice the the closing tag (</shape>) AUTOMATICALLY get updated. How cool is that! The <shape> elements are used because we will be creating a rectangular shape and adding a gradient to it.
  4. Enter the following highlighted code to create the NORMAL state of the listview item:

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <gradient
    android:angle="270" android:startColor="#f1f1f2"
    android:centerColor="#7878fa"
    android:endColor="#cfcfcf"> </gradient>

    </shape>

  5. Copy the custom_listview_normal file and paste it in the same folder but with the name of custom_listview_hover and change only the CENTER color values.
    NOTE: This file will be used to create the HOVER state. If you want to you can change one, two or all three colors.

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <gradient
    android:angle="270"
    android:centerColor="#5454f5"
    android:endColor="#cfcfcf"
    android:startColor="#f1f1f2"> </gradient>
    </shape>
  6. Right-click on the drawable folder again and select New > Drawable Resource File to create a list selector.
  7. In the Drawable Resource File dialog box that appears, in the File name text field enter custom_listview_selector, accept the other defaults and then click the OK button.
  8. Add the following highlighted <item> elements:

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/custom_listview_normal"
    android:state_pressed="false"
    android:state_selected="false">
    </item>

    <item android:drawable="@drawable/custom_listview_hover"
    android:state_pressed="true">
    </item>

    <item android:drawable="@drawable/custom_listview_hover"
    android:state_pressed="true"
    android:state_selected="true">
    </item>


    </selector>
    NOTE: The first <item> element is used if the list item has not been selected and have not been pressed--show the default background (e.g., custom_listview. The second <item> element is used if the list item is presses but not selected. The third <item> element is used if the list item is pressed and selected (user has press and then removed finger from selection).

  9. Add custom_listview_selector as an background attribute to the opening RelativeLayout element in the custom_listview_row.xml file.

    <?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:orientation="horizontal"
    android:padding="5dp"
    android:background="@drawable/custom_listview_selector">

  10. CHECK POINT: Preview the app in the emulator. You should see the normal state that as added to all list item. If you click on a list item it will change color to reflect the pressed or hover state.