Ящик навигации с заголовками/разделами

Я хотел бы знать, есть ли способ добавить заголовки/разделы в навигационный ящик. Мне удалось добавить что-то подобное, но оно отображается только в верхней части списка, потому что addHeaderView нужно вызывать перед setAdapter, и если я попытаюсь добавить больше элементов после setAdapter, они перепишут первые элементы.

Спасибо.

Edit:

public class MenuListAdapter extends BaseAdapter {

// Declare Variables
Context context;
String[] mTitle;
String[] mSubTitle;
int[] mIcon;
LayoutInflater inflater;

public MenuListAdapter(Context context, String[] title, String[] subtitle,
        int[] icon) {
    this.context = context;
    this.mTitle = title;
    this.mSubTitle = subtitle;
    this.mIcon = icon;

    inflater = (LayoutInflater) context
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

@Override
public int getViewTypeCount() {
    return super.getViewTypeCount();
}

@Override
public int getItemViewType(int position) {
    return super.getItemViewType(position);
}

@Override
public int getCount() {
    return mTitle.length;
}

@Override
public Object getItem(int position) {
    return mTitle[position];
}

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

public View getView(int position, View convertView, ViewGroup parent) {
    // Declare Variables
    TextView txtTitle;
    TextView txtSubTitle;
    ImageView imgIcon;

    View itemView = inflater.inflate(R.layout.drawer_list_item, parent,
            false);

    // Locate the TextViews in drawer_list_item.xml
    txtTitle = (TextView) itemView.findViewById(R.id.title);
    txtSubTitle = (TextView) itemView.findViewById(R.id.subtitle);

    // Locate the ImageView in drawer_list_item.xml
    imgIcon = (ImageView) itemView.findViewById(R.id.icon);

    // Set the results into TextViews
    txtTitle.setText(mTitle[position]);
    txtSubTitle.setText(mSubTitle[position]);

    // Set the results into ImageView
    imgIcon.setImageResource(mIcon[position]);

    return itemView;
}

}

EDIT:

Я нашел хорошее решение, объединив решения из разных источников, это основные классы, которые я использовал:

EntryAdapter

import java.util.ArrayList;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import com.androidbegin.sidemenututorial.R;

public class EntryAdapter extends ArrayAdapter<Item> {

    private enum RowType {
        LIST_ITEM, HEADER_ITEM
    }

    private Context context;
    private ArrayList<Item> items;
    private LayoutInflater vi;

    public EntryAdapter(Context context, ArrayList<Item> items) {
        super(context,0, items);
        this.context = context;
        this.items = items;
        vi = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getViewTypeCount() {  //Returns the number of types of Views that will be created by getView(int, View, ViewGroup).
        return RowType.values().length;
    }

    @Override
    public int getItemViewType(int position) { //framework calls getItemViewType for row n, the row it is about to display.
        //Get the type of View that will be created by getView(int, View, ViewGroup) for the specified item.
        Log.i("LIST", "item at " + position + " is " 
                + ((getItem(position).isSection() ? 0 : 1) == 0 ? "section" : "normal item"));
        return getItem(position).isSection() ? 0 : 1; // get position passes (n) and accertain  is its a header  or not
    }

    @Override
    public boolean isEnabled(int position) {
        return !getItem(position).isSection();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = convertView;

        final Item i = items.get(position);
        if (i != null) {
            if(i.isSection()){
                SectionItem si = (SectionItem) i;
                v = vi.inflate(R.layout.list_item_section, null);

                v.setOnClickListener(null);
                v.setOnLongClickListener(null);
                v.setLongClickable(false);

                final TextView sectionView = (TextView) v.findViewById(R.id.list_item_section_text);
                sectionView.setText(si.getTitle());
            }else{
                EntryItem ei = (EntryItem) i;
                v = vi.inflate(R.layout.list_item_entry, null);
                final TextView title = (TextView)v.findViewById(R.id.list_item_entry_title);
                final TextView subtitle = (TextView)v.findViewById(R.id.list_item_entry_summary);

                if (title != null) 
                    title.setText(ei.title);
                if(subtitle != null)
                    subtitle.setText(ei.subtitle);
            }
        }
        return v;
    }

}

EntryItem

public class EntryItem implements Item{

    public final String title;
    public final String subtitle;

    public EntryItem(String title, String subtitle) {
        this.title = title;
        this.subtitle = subtitle;
    }

    @Override
    public boolean isSection() {
        return false;
    }

}

Item

public interface Item {

    public boolean isSection();

}

SectionItem

public class SectionItem implements Item{

    private final String title;

    public SectionItem(String title) {
        this.title = title;
    }

    public String getTitle(){
        return title;
    }

    @Override
    public boolean isSection() {
        return true;
    }

}

MainActivity

import java.util.ArrayList;

import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.androidbegin.item.EntryAdapter;
import com.androidbegin.item.EntryItem;
import com.androidbegin.item.Item;
import com.androidbegin.item.SectionItem;

public class MainActivity extends SherlockFragmentActivity {

    // Declare Variable
    DrawerLayout mDrawerLayout;
    ListView mDrawerList;
    ActionBarDrawerToggle mDrawerToggle;
    MenuListAdapter mMenuAdapter;
    String[] title;
    String[] subtitle;
    int[] icon;
    Fragment fragment1 = new Fragment1();
    Fragment fragment2 = new Fragment2();
    Fragment fragment3 = new Fragment3();
    Context context;

    ArrayList<Item> items = new ArrayList<Item>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.drawer_main);
        this.context = this;
        // Generate title
        title = new String[] { "Title Fragment 1", "Title Fragment 2",
                "Title Fragment 3" };

        // Generate subtitle
        subtitle = new String[] { "Subtitle Fragment 1", "Subtitle Fragment 2",
                "Subtitle Fragment 3" };

        // Generate icon
        icon = new int[] { R.drawable.action_about, R.drawable.action_settings,
                R.drawable.collections_cloud };

        // Locate DrawerLayout in drawer_main.xml
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);

        // Locate ListView in drawer_main.xml
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        // Set a custom shadow that overlays the main content when the drawer
        // opens
        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow,
                GravityCompat.START);

        // Pass results to MenuListAdapter Class
//      mMenuAdapter = new MenuListAdapter(this, title, subtitle, icon);

        // Set the MenuListAdapter to the ListView
//      mDrawerList.setAdapter(mMenuAdapter);

        items.add(new SectionItem("Category 1"));
        items.add(new EntryItem("Item 1", "This is item 1.1"));
        items.add(new EntryItem("Item 2", "This is item 1.2"));
        items.add(new EntryItem("Item 3", "This is item 1.3"));


        items.add(new SectionItem("Category 2"));
        items.add(new EntryItem("Item 4", "This is item 2.1"));
        items.add(new EntryItem("Item 5", "This is item 2.2"));
        items.add(new EntryItem("Item 6", "This is item 2.3"));
        items.add(new EntryItem("Item 7", "This is item 2.4"));

        items.add(new SectionItem("Category 3"));
        items.add(new EntryItem("Item 8", "This is item 3.1"));
        items.add(new EntryItem("Item 9", "This is item 3.2"));
        items.add(new EntryItem("Item 10", "This is item 3.3"));
        items.add(new EntryItem("Item 11", "This is item 3.4"));
        items.add(new EntryItem("Item 12", "This is item 3.5"));

        EntryAdapter adapter = new EntryAdapter(this, items);

        mDrawerList.setAdapter(adapter);

        // Capture button clicks on side menu
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

        // Enable ActionBar app icon to behave as action to toggle nav drawer
        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        // ActionBarDrawerToggle ties together the the proper interactions
        // between the sliding drawer and the action bar app icon
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_drawer, R.string.drawer_open,
                R.string.drawer_close) {

            public void onDrawerClosed(View view) {
                // TODO Auto-generated method stub
                super.onDrawerClosed(view);
            }

            public void onDrawerOpened(View drawerView) {
                // TODO Auto-generated method stub
                super.onDrawerOpened(drawerView);
            }
        };

        mDrawerLayout.setDrawerListener(mDrawerToggle);

        if (savedInstanceState == null) {
            selectItem(0);
        }
    }

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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        if (item.getItemId() == android.R.id.home) {

            if (mDrawerLayout.isDrawerOpen(mDrawerList)) {
                mDrawerLayout.closeDrawer(mDrawerList);
            } else {
                mDrawerLayout.openDrawer(mDrawerList);
            }
        }

        return super.onOptionsItemSelected(item);
    }

    // The click listener for ListView in the navigation drawer
    private class DrawerItemClickListener implements
            ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position,
                long id) {
            Log.i("LIST", "item position = " + Integer.toString(position)
                    + "\nitem id = " + String.valueOf(id));
            if (!items.get(position).isSection()) {
                EntryItem item = (EntryItem)items.get(position);

                Toast.makeText(context, "You clicked " + item.title , Toast.LENGTH_SHORT).show();

                selectItem(position);
            }
//          selectItem(position);
        }
    }

    private void selectItem(int position) {

        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        // Locate Position
        switch (position) {
            case 0:
                ft.replace(R.id.content_frame, fragment1);
                break;
            case 1:
                ft.replace(R.id.content_frame, fragment2);
                break;
            case 2:
                ft.replace(R.id.content_frame, fragment3);
                break;
        }
        ft.commit();
        mDrawerList.setItemChecked(position, true);
        // Close drawer
        mDrawerLayout.closeDrawer(mDrawerList);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Pass any configuration change to the drawer toggles
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
}

Но у меня есть небольшая проблема: разделы занимают позицию в списке, а выбор фрагментов испорчен.

Ответ 1

Вы добавляете заголовки/разделы в ListView для использования в DrawerLayout так же, как вы добавляете заголовки/разделы в ListView для использования в любом месте внутри Android.

На низком уровне это включает ListAdapter, который:

  • Переопределяет getViewTypeCount(), чтобы указать, сколько различных типов строк есть (например, 2, один для заголовков и один для регулярных строк)

  • Переопределяет getItemViewType(), чтобы указать, какой тип строки использовать для заданного position

  • Обеспечивает, чтобы getView() (или newView()/bindView() CursorAdapter) знал о нескольких типах строк и обрабатывал их соответственно

На более высоком уровне вы можете попытаться использовать такие вещи, как my MergeAdapter или различные другие сторонние библиотеки, чтобы упростить это.

Ответ 2

В навигационном ящике список элементов можно отобразить с помощью ListView, чтобы вы могли иметь адаптер класса и реализовывать свою логику. Таким образом, вы можете добавлять разделы, заголовки и т.д.

Ответ 3

Здесь у вас есть полный пример навигационного ящика с использованием заголовков/секций

Это результат

enter image description here

Ответ 4

Если ваши элементы списка фиксированы (не изменяются), быстрый "взломать" должен включать в себя случай переключения для "позиции" в вашем методе getView() и раздувать файл headerlayout.xml на этих фиксированных позициях. Ваша регулярная инфляция войдет в стандартную часть корпуса коммутатора. Он грязный и не рекомендуется, но эффективен.

Ответ 5

Я бы предложил расширить EntryItem, добавив элемент тега, который сообщает вам, какой тип фрагмента будет создан. Затем просто проверьте тег в вашем обработчике onItemClick, чтобы создать правильный тип фрагмента. Таким образом, вы не зависите от позиции, которая может измениться при добавлении/удалении элементов в разделах.

Ответ 6

Вам нужно добавить это в класс EntryAdapter:

@Override
public boolean areAllItemsEnabled () {
    return false;
}

с этим и:

@Override
public boolean isEnabled(int position) {
    return !getItem(position).isSection();
}

Разделы не должны занимать позицию в ListView.

Ответ 7

Хорошим решением, которое я прочитал, было размещение заголовка TextView внутри вашего макета строки и установление его видимости на GONE.

Затем в вашем адаптере getView укажите некоторую логику, которая гласит: "Это самый первый элемент в списке (позиция 0), OR - это тип этого элемента, отличный от позиции типа один над ним? Если это так, измените видимость заголовка TextView's на VISIBLE.

Этот способ предпочтительнее, потому что, когда вы хотите использовать getItemAtPosition, вам не нужно определять, как уклоняться от заголовков разделов, поскольку они будут занимать всю позицию, если вы внедрили их так, как OP и другие предложил.