1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

Replaced the ListView main menu with a ScrollView and an AdapterLinearLayout.

It makes the layout simpler and fixes focus issues in keypad navigation mode.
This commit is contained in:
Christophe Beyls 2015-06-24 13:17:58 +02:00
parent 0c2ddc73e0
commit b6cc5513db
8 changed files with 242 additions and 99 deletions

View file

@ -43,10 +43,8 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -62,13 +60,14 @@ import be.digitalia.fosdem.fragments.LiveFragment;
import be.digitalia.fosdem.fragments.MapFragment; import be.digitalia.fosdem.fragments.MapFragment;
import be.digitalia.fosdem.fragments.PersonsListFragment; import be.digitalia.fosdem.fragments.PersonsListFragment;
import be.digitalia.fosdem.fragments.TracksFragment; import be.digitalia.fosdem.fragments.TracksFragment;
import be.digitalia.fosdem.widgets.AdapterLinearLayout;
/** /**
* Main entry point of the application. Allows to switch between section fragments and update the database. * Main entry point of the application. Allows to switch between section fragments and update the database.
* *
* @author Christophe Beyls * @author Christophe Beyls
*/ */
public class MainActivity extends ActionBarActivity implements ListView.OnItemClickListener { public class MainActivity extends ActionBarActivity {
private enum Section { private enum Section {
TRACKS(TracksFragment.class, R.string.menu_tracks, R.drawable.ic_event_grey600_24dp, true), TRACKS(TracksFragment.class, R.string.menu_tracks, R.drawable.ic_event_grey600_24dp, true),
@ -225,23 +224,16 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
// Setup Main menu // Setup Main menu
mainMenu = findViewById(R.id.main_menu); mainMenu = findViewById(R.id.main_menu);
ListView menuListView = (ListView) findViewById(R.id.main_menu_list); final AdapterLinearLayout sectionsList = (AdapterLinearLayout) findViewById(R.id.sections);
LayoutInflater inflater = LayoutInflater.from(this); menuAdapter = new MainMenuAdapter(getLayoutInflater());
View menuHeaderView = inflater.inflate(R.layout.header_main_menu, null); sectionsList.setAdapter(menuAdapter);
menuListView.addHeaderView(menuHeaderView, null, false); mainMenu.findViewById(R.id.settings).setOnClickListener(menuFooterClickListener);
View menuFooterView = inflater.inflate(R.layout.footer_main_menu, null); mainMenu.findViewById(R.id.about).setOnClickListener(menuFooterClickListener);
menuFooterView.findViewById(R.id.settings).setOnClickListener(menuFooterClickListener);
menuFooterView.findViewById(R.id.about).setOnClickListener(menuFooterClickListener);
menuListView.addFooterView(menuFooterView, null, false);
LocalBroadcastManager.getInstance(this).registerReceiver(scheduleRefreshedReceiver, new IntentFilter(DatabaseManager.ACTION_SCHEDULE_REFRESHED)); LocalBroadcastManager.getInstance(this).registerReceiver(scheduleRefreshedReceiver, new IntentFilter(DatabaseManager.ACTION_SCHEDULE_REFRESHED));
menuAdapter = new MainMenuAdapter(inflater); // Last update date, below the list
menuListView.setAdapter(menuAdapter); lastUpdateTextView = (TextView) mainMenu.findViewById(R.id.last_update);
menuListView.setOnItemClickListener(this);
// Last update date, below the menu
lastUpdateTextView = (TextView) findViewById(R.id.last_update);
updateLastUpdateTime(); updateLastUpdateTime();
// Restore current section // Restore current section
@ -254,7 +246,18 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
currentSection = Section.values()[savedInstanceState.getInt(STATE_CURRENT_SECTION)]; currentSection = Section.values()[savedInstanceState.getInt(STATE_CURRENT_SECTION)];
} }
// Ensure the current section is visible in the menu // Ensure the current section is visible in the menu
menuListView.setSelection(currentSection.ordinal()); sectionsList.post(new Runnable() {
@Override
public void run() {
if (sectionsList.getChildCount() > currentSection.ordinal()) {
ScrollView mainMenuScrollView = (ScrollView) findViewById(R.id.main_menu_scroll);
int requiredScroll = sectionsList.getTop()
+ sectionsList.getChildAt(currentSection.ordinal()).getBottom()
- mainMenuScrollView.getHeight();
mainMenuScrollView.scrollTo(0, Math.max(0, requiredScroll));
}
}
});
updateActionBar(); updateActionBar();
} }
@ -452,12 +455,11 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
} }
}; };
private class MainMenuAdapter extends BaseAdapter { private class MainMenuAdapter extends AdapterLinearLayout.Adapter<Section> {
private final Section[] sections = Section.values(); private final Section[] sections = Section.values();
private final LayoutInflater inflater; private final LayoutInflater inflater;
private final int currentSectionForegroundColor; private final int currentSectionForegroundColor;
private final int currentSectionBackgroundColor;
public MainMenuAdapter(LayoutInflater inflater) { public MainMenuAdapter(LayoutInflater inflater) {
this.inflater = inflater; this.inflater = inflater;
@ -468,7 +470,6 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
} finally { } finally {
a.recycle(); a.recycle();
} }
currentSectionBackgroundColor = getResources().getColor(R.color.translucent_grey);
} }
@Override @Override
@ -481,45 +482,37 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
return sections[position]; return sections[position];
} }
@Override
public long getItemId(int position) {
return position;
}
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
convertView = inflater.inflate(R.layout.item_main_menu, parent, false); convertView = inflater.inflate(R.layout.item_main_menu, parent, false);
convertView.setOnClickListener(sectionClickListener);
} }
Section section = getItem(position); Section section = getItem(position);
convertView.setSelected(section == currentSection);
TextView tv = (TextView) convertView.findViewById(R.id.section_text); TextView tv = (TextView) convertView.findViewById(R.id.section_text);
SpannableString sectionTitle = new SpannableString(getString(section.getTitleResId())); SpannableString sectionTitle = new SpannableString(getString(section.getTitleResId()));
Drawable sectionIcon = getResources().getDrawable(section.getIconResId()); Drawable sectionIcon = getResources().getDrawable(section.getIconResId());
int backgroundColor;
if (section == currentSection) { if (section == currentSection) {
// Special color for the current section // Special color for the current section
//sectionTitle.setSpan(new StyleSpan(Typeface.BOLD), 0, sectionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //sectionTitle.setSpan(new StyleSpan(Typeface.BOLD), 0, sectionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sectionTitle.setSpan(new ForegroundColorSpan(currentSectionForegroundColor), 0, sectionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); sectionTitle.setSpan(new ForegroundColorSpan(currentSectionForegroundColor), 0, sectionTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// We need to mutate the drawable before applying the ColorFilter, or else all the similar drawable instances will be tinted. // We need to mutate the drawable before applying the ColorFilter, or else all the similar drawable instances will be tinted.
sectionIcon.mutate().setColorFilter(currentSectionForegroundColor, PorterDuff.Mode.SRC_IN); sectionIcon.mutate().setColorFilter(currentSectionForegroundColor, PorterDuff.Mode.SRC_IN);
backgroundColor = currentSectionBackgroundColor;
} else {
backgroundColor = Color.TRANSPARENT;
} }
tv.setText(sectionTitle); tv.setText(sectionTitle);
tv.setCompoundDrawablesWithIntrinsicBounds(sectionIcon, null, null, null); tv.setCompoundDrawablesWithIntrinsicBounds(sectionIcon, null, null, null);
tv.setBackgroundColor(backgroundColor);
return convertView; return convertView;
} }
} }
private final View.OnClickListener sectionClickListener = new View.OnClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onClick(View view) {
// Decrease position by 1 since the listView has a header view. Section section = menuAdapter.getItem(((ViewGroup) view.getParent()).indexOfChild(view));
Section section = menuAdapter.getItem(position - 1);
if (section != currentSection) { if (section != currentSection) {
// Switch to new section // Switch to new section
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
@ -536,7 +529,7 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
if (section.shouldKeep() && ((f = fm.findFragmentByTag(fragmentClassName)) != null)) { if (section.shouldKeep() && ((f = fm.findFragmentByTag(fragmentClassName)) != null)) {
ft.attach(f); ft.attach(f);
} else { } else {
f = Fragment.instantiate(this, fragmentClassName); f = Fragment.instantiate(MainActivity.this, fragmentClassName);
ft.add(R.id.content, f, fragmentClassName); ft.add(R.id.content, f, fragmentClassName);
} }
ft.commit(); ft.commit();
@ -547,6 +540,7 @@ public class MainActivity extends ActionBarActivity implements ListView.OnItemCl
drawerLayout.closeDrawer(mainMenu); drawerLayout.closeDrawer(mainMenu);
} }
};
public static class AboutDialogFragment extends DialogFragment { public static class AboutDialogFragment extends DialogFragment {

View file

@ -0,0 +1,123 @@
package be.digitalia.fosdem.widgets;
import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
/**
* Vertical LinearLayout populated by a special adapter.
*
* @author Christophe Beyls
*/
public class AdapterLinearLayout extends LinearLayout {
/**
* Implement this Adapter to populate the layout.
* Call notifyDataSetChanged() to update it.
*/
public static abstract class Adapter<T> {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
private void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
private void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public abstract int getCount();
public abstract T getItem(int position);
public abstract View getView(int position, View convertView, ViewGroup parent);
}
private class AdapterLinearLayoutDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
populateFromAdapter();
}
}
private Adapter mAdapter;
private AdapterLinearLayoutDataSetObserver mDataSetObserver;
public AdapterLinearLayout(Context context) {
this(context, null);
}
public AdapterLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
}
public void setAdapter(Adapter adapter) {
if (mAdapter == adapter) {
return;
}
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
removeAllViews();
mAdapter = adapter;
if (adapter != null && mDataSetObserver != null) {
populateFromAdapter();
adapter.registerDataSetObserver(mDataSetObserver);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mDataSetObserver = new AdapterLinearLayoutDataSetObserver();
if (mAdapter != null) {
populateFromAdapter();
mAdapter.registerDataSetObserver(mDataSetObserver);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
mDataSetObserver = null;
}
private void populateFromAdapter() {
final Adapter adapter = mAdapter;
final int currentCount = getChildCount();
final int newCount = adapter.getCount();
final int commonCount = Math.min(currentCount, newCount);
// 1. Update common views
for (int i = 0; i < commonCount; ++i) {
final View currentView = getChildAt(i);
final View newView = adapter.getView(i, currentView, this);
if (currentView != newView) {
// Edge case: View is not recycled
removeViewAt(i);
addView(newView, i);
}
}
// 2a. Add missing views
for (int i = commonCount; i < newCount; ++i) {
addView(adapter.getView(i, null, this));
}
// 2b. Remove extra views (starting from the end to avoid array copies)
for (int i = currentCount - 1; i >= commonCount; --i) {
removeViewAt(i);
}
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector>
<item
android:drawable="@color/translucent_grey"
android:state_selected="true"/>
<item android:drawable="@android:color/transparent"/>
</selector>
</item>
<!-- Usage of theme attributes in an XML drawable is only allowed in API 21+ -->
<item android:drawable="?attr/selectableItemBackground"/>
</layer-list>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector>
<item
android:drawable="@color/translucent_grey"
android:state_selected="true"/>
<item android:drawable="@android:color/transparent"/>
</selector>
</item>
<item android:drawable="@drawable/abc_item_background_holo_light"/>
</layer-list>

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
style="@style/SeparatorLine"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/settings"
style="@style/MainMenuItem"
android:background="?selectableItemBackground"
android:text="@string/settings"/>
<TextView
android:id="@+id/about"
style="@style/MainMenuItem"
android:background="?selectableItemBackground"
android:text="@string/about"/>
</LinearLayout>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/main_menu_padding"
android:scaleType="center"
android:src="@drawable/fosdem_title"/>

View file

@ -35,19 +35,49 @@
android:layout_width="260dp" android:layout_width="260dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="left" android:layout_gravity="left"
android:background="@color/main_menu_background"
android:orientation="vertical"> android:orientation="vertical">
<ListView <ScrollView
android:id="@+id/main_menu_list" android:id="@+id/main_menu_scroll"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:cacheColorHint="@android:color/transparent" android:background="@color/main_menu_background">
android:divider="@null"
android:dividerHeight="0dp" <LinearLayout
tools:listheader="@layout/header_main_menu" android:layout_width="match_parent"
tools:listitem="@layout/item_main_menu"/> android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/main_menu_padding"
android:scaleType="center"
android:src="@drawable/fosdem_title"/>
<be.digitalia.fosdem.widgets.AdapterLinearLayout
android:id="@+id/sections"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<View
style="@style/SeparatorLine"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"/>
<TextView
android:id="@+id/settings"
style="@style/MainMenuItem"
android:text="@string/settings"/>
<TextView
android:id="@+id/about"
style="@style/MainMenuItem"
android:text="@string/about"/>
</LinearLayout>
</ScrollView>
<TextView <TextView
android:id="@+id/last_update" android:id="@+id/last_update"

View file

@ -61,6 +61,8 @@
<!-- Styles --> <!-- Styles -->
<style name="MainMenuItem" parent="TextAppearance.AppCompat.Body2"> <style name="MainMenuItem" parent="TextAppearance.AppCompat.Body2">
<item name="android:background">@drawable/menu_item_background</item>
<item name="android:focusable">true</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/main_menu_item_height</item> <item name="android:layout_height">@dimen/main_menu_item_height</item>
<item name="android:gravity">left|center_vertical</item> <item name="android:gravity">left|center_vertical</item>