מדריך לדוגמא של Android ListView עם מתאם מותאם אישית

במדריך זה, נשתמש ב־CustomAdapter שממלא את השורות המותאמות אישית של ה־Android ListView עם ArrayList. גם כדי לשדרג את חוויית המשתמש, נוסיף אנימציה ל־ListView בעת גלילה.

סקירת Custom Adapter ל־Android ListView

האדפטר הפשוט ביותר למילוי תצוגה מ־ArrayList הוא ArrayAdapter. זה מה שנממש במדריך זה. ישנם אדפטרים נוספים כמו CursorAdapter שמקשר ישירות ל־result set מ־מסד נתונים SQLite מקומי והוא משתמש ב־Cursor כמקור נתונים.

מחזור יצירת שורות

כש־ListView נוצר והשורות מולאות כך שהגובה המלא של הרשימה מתמלא, לא נוצרים פריטי שורה חדשים בזיכרון. בעוד המשתמש גולל ברשימה, פריטים שעוזבים את המסך נשמרים בזיכרון לשימוש מאוחר יותר ואז כל שורה חדשה שנכנסת למסך משתמשת בשורה ישנה ששומרת בזיכרון.

יצירת תבנית תצוגה

בואו ניצור תבנית XML שמציגה את הפריטים בשורה בדרך מותאמת אישית. row_item.xml

<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp">

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:text="Marshmallow"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:textColor="@android:color/black" />


    <TextView
        android:id="@+id/type"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/name"
        android:layout_marginTop="5dp"
        android:text="Android 6.0"
        android:textColor="@android:color/black" />

    <ImageView
        android:id="@+id/item_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@android:drawable/ic_dialog_info" />


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true">

        <TextView
            android:id="@+id/version_heading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="API: "
            android:textColor="@android:color/black"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/version_number"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="23"
            android:textAppearance="?android:attr/textAppearanceButton"
            android:textColor="@android:color/black"
            android:textStyle="bold" />

    </LinearLayout>

</RelativeLayout>

במדריך זה נבנה אפליקציה שמורכבת מרשימת שורות המציגות תיאורי טקסט וסמל מידע. לחיצה על השורה תציג את ה-SnackBar עם אלמנטי הטקסט של אותה שורה. לחיצה על המידע תציג SnackBar עם מידע ספציפי לשורה הזו.

מבנה הפרויקט

קוד

אנו יוצרים ListView מותאם אישית על ידי תת-מחלקת ArrayAdapter עם DataModel כאובייקט. getView() הוא השיטה שמחזירה את התצוגה האמיתית המשמשת כשורה בתוך ה-ListView במיקום מסוים. ה-content_main.xml מכיל את ה-ListView כפי שמוצג למטה. content_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    tools:context="com.journaldev.customlistview.MainActivity"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/activity_main">

    <ListView
        android:id="@+id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</RelativeLayout>

הדגמה של הנתונים המכילים ב-ArrayList מוצגת למטה. DataModel.java

public class DataModel {

    String name;
    String type;
    String version_number;
    String feature;

    public DataModel(String name, String type, String version_number, String feature ) {
        this.name=name;
        this.type=type;
        this.version_number=version_number;
        this.feature=feature;

    }

    public String getName() {
        return name;
    }
    
    public String getType() {
        return type;
    }
    
    public String getVersion_number() {
        return version_number;
    }
    
    public String getFeature() {
        return feature;
    }
    
}

ה-Adapter המותאם אישית שמאכלס את ה-DataModel לתוך ה-ListView מוצג למטה. CustomAdapter.java

public class CustomAdapter extends ArrayAdapter implements View.OnClickListener{

    private ArrayList dataSet;
    Context mContext;

    // מטמון חיפוש תצוגה
    private static class ViewHolder {
        TextView txtName;
        TextView txtType;
        TextView txtVersion;
        ImageView info;
    }

    public CustomAdapter(ArrayList data, Context context) {
        super(context, R.layout.row_item, data);
        this.dataSet = data;
        this.mContext=context;

    }

    @Override
    public void onClick(View v) {

        int position=(Integer) v.getTag();
        Object object= getItem(position);
        DataModel dataModel=(DataModel)object;

        switch (v.getId())
        {
            case R.id.item_info:
                Snackbar.make(v, "Release date " +dataModel.getFeature(), Snackbar.LENGTH_LONG)
                        .setAction("No action", null).show();
                break;
        }
    }

    private int lastPosition = -1;

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // קבל את פריט הנתונים למיקום זה
        DataModel dataModel = getItem(position);
        // בדוק אם תצוגה קיימת ממוחזרת, אחרת נפח את התצוגה
        ViewHolder viewHolder; // view lookup cache stored in tag

        final View result;

        if (convertView == null) {

            viewHolder = new ViewHolder();
            LayoutInflater inflater = LayoutInflater.from(getContext());
            convertView = inflater.inflate(R.layout.row_item, parent, false);
            viewHolder.txtName = (TextView) convertView.findViewById(R.id.name);
            viewHolder.txtType = (TextView) convertView.findViewById(R.id.type);
            viewHolder.txtVersion = (TextView) convertView.findViewById(R.id.version_number);
            viewHolder.info = (ImageView) convertView.findViewById(R.id.item_info);

            result=convertView;

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
            result=convertView;
        }

        Animation animation = AnimationUtils.loadAnimation(mContext, (position > lastPosition) ? R.anim.up_from_bottom : R.anim.down_from_top);
        result.startAnimation(animation);
        lastPosition = position;

        viewHolder.txtName.setText(dataModel.getName());
        viewHolder.txtType.setText(dataModel.getType());
        viewHolder.txtVersion.setText(dataModel.getVersion_number());
        viewHolder.info.setOnClickListener(this);
        viewHolder.info.setTag(position);
        // החזר את התצוגה המושלמת להצגה על המסך
        return convertView;
    }
}

בקוד למעלה הוספנו onClickListener ל-ImageView שמציג SnackBar כאשר לוחצים עליו עם תיאור לשורה המתאימה. גם שורות הרשימה מנומסות בעת גלילה. קבצי משאבי האנימציה השניים נתונים למטה. down_from_top.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="https://schemas.android.com/apk/res/android"
    android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
        android:fromXDelta="0%" android:toXDelta="0%"
        android:fromYDelta="-100%" android:toYDelta="0%"
        android:duration="400" />
</set>

up_from_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="https://schemas.android.com/apk/res/android"
    android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
        android:fromXDelta="0%" android:toXDelta="0%"
        android:fromYDelta="100%" android:toYDelta="0%"
        android:duration="400" />
</set>

ה-MainActivity.java שבו ה-Adapter המותאם אישית מוגדר ל-ListView מוגדר למטה. לצד זאת, ArrayList אקראי של אובייקטים של DataModel מאוכלס. MainActivity.java

public class MainActivity extends AppCompatActivity {

    ArrayList dataModels;
    ListView listView;
    private static CustomAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        listView=(ListView)findViewById(R.id.list);

        dataModels= new ArrayList<>();

        dataModels.add(new DataModel("Apple Pie", "Android 1.0", "1","September 23, 2008"));
        dataModels.add(new DataModel("Banana Bread", "Android 1.1", "2","February 9, 2009"));
        dataModels.add(new DataModel("Cupcake", "Android 1.5", "3","April 27, 2009"));
        dataModels.add(new DataModel("Donut","Android 1.6","4","September 15, 2009"));
        dataModels.add(new DataModel("Eclair", "Android 2.0", "5","October 26, 2009"));
        dataModels.add(new DataModel("Froyo", "Android 2.2", "8","May 20, 2010"));
        dataModels.add(new DataModel("Gingerbread", "Android 2.3", "9","December 6, 2010"));
        dataModels.add(new DataModel("Honeycomb","Android 3.0","11","February 22, 2011"));
        dataModels.add(new DataModel("Ice Cream Sandwich", "Android 4.0", "14","October 18, 2011"));
        dataModels.add(new DataModel("Jelly Bean", "Android 4.2", "16","July 9, 2012"));
        dataModels.add(new DataModel("Kitkat", "Android 4.4", "19","October 31, 2013"));
        dataModels.add(new DataModel("Lollipop","Android 5.0","21","November 12, 2014"));
        dataModels.add(new DataModel("Marshmallow", "Android 6.0", "23","October 5, 2015"));

        adapter= new CustomAdapter(dataModels,getApplicationContext());

        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {

                DataModel dataModel= dataModels.get(position);

                Snackbar.make(view, dataModel.getName()+"\n"+dataModel.getType()+" API: "+dataModel.getVersion_number(), Snackbar.LENGTH_LONG)
                        .setAction("No action", null).show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // נפח את התפריט; פעולה זו מוסיפה פריטים לסרגל הפעולות אם הוא קיים.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // טפל בלחיצות פריטי סרגל הפעולות כאן. סרגל הפעולות יטפל
        // אוטומטית בלחיצות על הכפתור הבית/למעלה, כל עוד
        // אתה מציין פעילות הורה ב-AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

הפלט של היישום בפעולה מוצג למטה. מתקרבת סוף המדריך. תוכל להוריד את פרויקט המתאם המותאם אישית של ListView של Android הסופי מהקישור למטה.

הורדת פרויקט המתאם המותאם אישית של ListView של Android

התייחסות: מדריך API של ListView

Source:
https://www.digitalocean.com/community/tutorials/android-listview-with-custom-adapter-example-tutorial