com.androzic.MapList.java Source code

Java tutorial

Introduction

Here is the source code for com.androzic.MapList.java

Source

/*
 * Androzic - android navigation client that uses OziExplorer maps (ozf2, ozfx3).
 * Copyright (C) 2010-2014  Andrey Novikov <http://andreynovikov.info/>
 *
 * This file is part of Androzic application.
 *
 * Androzic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
    
 * Androzic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
    
 * You should have received a copy of the GNU General Public License
 * along with Androzic.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.androzic;

import java.io.File;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.ListFragment;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.androzic.map.BaseMap;
import com.androzic.map.OnMapActionListener;
import com.androzic.map.online.OnlineMap;

public class MapList extends ListFragment {
    private final Handler handler = new Handler();

    private OnMapActionListener mapActionsCallback;
    private MapListAdapter adapter;
    private TreeNode<BaseMap> mapsTree = new TreeNode<>();
    private MapComparator mapComparator = new MapComparator();
    private boolean populated;
    private TreeNode<BaseMap> currentTree;
    private ProgressBar progressBar;
    private int shortAnimationDuration;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
        currentTree = mapsTree;
        populated = false;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.list_with_empty_view_and_progressbar, container, false);

        progressBar = (ProgressBar) view.findViewById(R.id.progressbar);

        view.setFocusableInTouchMode(true);
        view.requestFocus();
        view.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                    if (currentTree != mapsTree) {
                        currentTree = currentTree.parent;
                        adapter.notifyDataSetChanged();
                        return true;
                    }
                }
                return false;
            }
        });

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setHasOptionsMenu(true);

        TextView emptyView = (TextView) getListView().getEmptyView();
        if (emptyView != null)
            emptyView.setText(R.string.msg_empty_map_list);

        Activity activity = getActivity();

        adapter = new MapListAdapter(activity);
        setListAdapter(adapter);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // This makes sure that the container activity has implemented
        // the callback interface. If not, it throws an exception
        try {
            mapActionsCallback = (OnMapActionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnMapActionListener");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.maplist_name);
        if (!populated)
            populateItems();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.maplist_menu, menu);
    }

    @Override
    public void onPrepareOptionsMenu(final Menu menu) {
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
        boolean useIndex = settings.getBoolean(getString(R.string.pref_usemapindex),
                getResources().getBoolean(R.bool.def_usemapindex));
        menu.findItem(R.id.action_reset_index).setEnabled(useIndex);
    }

    @SuppressLint("NewApi")
    private void crossfade(boolean direct) {
        View listView = adapter.getCount() > 0 ? getListView() : getListView().getEmptyView();
        final View from = direct ? progressBar : listView;
        final View to = direct ? listView : progressBar;

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
            from.setVisibility(View.GONE);
            to.setVisibility(View.VISIBLE);
        } else {
            // Set the content view to 0% opacity but visible, so that it is visible
            // (but fully transparent) during the animation.
            to.setAlpha(0f);
            to.setVisibility(View.VISIBLE);

            // Animate the content view to 100% opacity, and clear any animation
            // listener set on the view.
            to.animate().alpha(1f).setDuration(shortAnimationDuration).setListener(null);

            // Animate the loading view to 0% opacity. After the animation ends,
            // set its visibility to GONE as an optimization step (it won't
            // participate in layout passes, etc.)
            from.animate().alpha(0f).setDuration(shortAnimationDuration).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    from.setVisibility(View.GONE);
                }
            });
        }
    }

    private void populateItems() {
        crossfade(false);

        new Thread(new Runnable() {
            public void run() {
                populated = true;
                Androzic application = Androzic.getApplication();
                TreeNode<BaseMap> onlinemaps = null;
                String mappath = application.getMapPath();

                for (BaseMap map : application.getMaps()) {
                    if (map instanceof OnlineMap) {
                        if (onlinemaps == null)
                            onlinemaps = mapsTree.addChild(getResources().getString(R.string.online_maps));
                        onlinemaps.addChild(map.path, map);
                    } else {
                        String fn = map.path;
                        if (fn.startsWith(mappath)) {
                            fn = fn.substring(mappath.length() + 1);
                        }
                        String[] components = fn.split(File.separator);
                        TreeNode<BaseMap> folder = mapsTree;
                        for (int i = 0; i < components.length - 1; i++) {
                            TreeNode<BaseMap> subfolder = folder.findChild(components[i]);
                            if (subfolder == null)
                                subfolder = folder.addChild(components[i]);
                            folder = subfolder;
                        }
                        folder.addChild(components[components.length - 1], map);
                    }

                    mapsTree.sort(mapComparator);
                    currentTree = mapsTree;
                }

                handler.post(updateResults);
            }
        }).start();
    }

    final Runnable updateList = new Runnable() {
        public void run() {
            populateItems();
        }
    };

    final Runnable updateResults = new Runnable() {
        public void run() {
            adapter.notifyDataSetChanged();
            crossfade(true);
        }
    };

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        TreeNode<BaseMap> item = currentTree.children.get(position);
        if (item.data != null) {
            mapActionsCallback.onMapSelected(item.data);
            getFragmentManager().popBackStack();
        } else {
            item.sort(mapComparator);
            currentTree = item;
            adapter.notifyDataSetChanged();
        }
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_reset_index:
            crossfade(false);
            mapsTree.clear();
            currentTree = mapsTree;
            adapter.notifyDataSetChanged();

            new Thread(new Runnable() {
                public void run() {
                    Androzic application = Androzic.getApplication();
                    application.resetMaps();
                    handler.post(updateList);
                }
            }).start();
            break;
        }
        return true;
    }

    public class MapListAdapter extends BaseAdapter {
        private static final int VIEW_TYPE_FOLDER = 0;
        private static final int VIEW_TYPE_MAP = 1;

        private LayoutInflater mInflater;
        private double ppcm;

        public MapListAdapter(Context context) {
            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
            Resources resources = getResources();
            boolean scaleInMeters = settings.getBoolean(getString(R.string.pref_maplistscale),
                    resources.getBoolean(R.bool.def_maplistscale));

            DisplayMetrics metrics = resources.getDisplayMetrics();
            ppcm = metrics.xdpi / 2.54;
            if (!scaleInMeters)
                ppcm *= 100;
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public int getItemViewType(int position) {
            TreeNode<BaseMap> item = getItem(position);
            if (item.data != null) {
                return VIEW_TYPE_MAP;
            } else {
                return VIEW_TYPE_FOLDER;
            }
        }

        @Override
        public TreeNode<BaseMap> getItem(int position) {
            return currentTree.children.get(position);
        }

        @Override
        public long getItemId(int position) {
            return currentTree.children.get(position).getId();
        }

        @Override
        public int getCount() {
            return currentTree.children.size();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            MapListItemHolder itemHolder;
            TreeNode<BaseMap> item = getItem(position);

            int type = getItemViewType(position);
            if (convertView == null) {
                itemHolder = new MapListItemHolder();
                if (type == VIEW_TYPE_FOLDER) {
                    convertView = mInflater.inflate(R.layout.list_item_folder, parent, false);
                    itemHolder.name = (TextView) convertView.findViewById(R.id.name);
                } else if (type == VIEW_TYPE_MAP) {
                    convertView = mInflater.inflate(R.layout.list_item_map, parent, false);
                    itemHolder.name = (TextView) convertView.findViewById(R.id.name);
                    itemHolder.scale = (TextView) convertView.findViewById(R.id.scale);
                    itemHolder.filename = (TextView) convertView.findViewById(R.id.filename);
                } else {
                    return null;
                }
                convertView.setTag(itemHolder);
            } else {
                itemHolder = (MapListItemHolder) convertView.getTag();
            }

            if (type == VIEW_TYPE_FOLDER) {
                itemHolder.name.setText(item.name);
            } else if (type == VIEW_TYPE_MAP) {
                itemHolder.name.setText(item.data.title);
                itemHolder.filename.setText(item.name);
                itemHolder.scale.setText(String.format("1:%,d", (int) (item.data.getAbsoluteMPP() * ppcm)));
            }

            return convertView;
        }

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

    private static class MapListItemHolder {
        TextView name;
        TextView scale;
        TextView filename;
    }

    public class TreeNode<T> {
        String name;
        T data;
        TreeNode<T> parent;
        List<TreeNode<T>> children;

        public TreeNode() {
            this.children = new LinkedList<>();
        }

        public TreeNode(String name) {
            this();
            this.name = name;
        }

        public TreeNode(String name, T data) {
            this(name);
            this.data = data;
        }

        public TreeNode<T> addChild(String name) {
            TreeNode<T> childNode = new TreeNode<>(name);
            childNode.parent = this;
            children.add(childNode);
            return childNode;
        }

        public TreeNode<T> addChild(String name, T child) {
            TreeNode<T> childNode = new TreeNode<>(name, child);
            childNode.parent = this;
            children.add(childNode);
            return childNode;
        }

        public TreeNode<T> findChild(String name) {
            for (TreeNode<T> child : children) {
                if (name.equals(child.name))
                    return child;
            }
            return null;
        }

        public long getId() {
            if (data != null)
                return data.hashCode();
            else
                return name.hashCode();
        }

        public void clear() {
            for (TreeNode<T> child : children) {
                child.clear();
            }
            children.clear();
        }

        public void sort(Comparator<TreeNode<T>> comparator) {
            Collections.sort(children, comparator);
        }
    }

    class MapComparator implements Comparator<TreeNode<BaseMap>> {
        @Override
        public int compare(TreeNode<BaseMap> a, TreeNode<BaseMap> b) {
            if (a.data != null && b.data != null)
                return a.data.title.compareToIgnoreCase(b.data.title);
            if (a.data != null)
                return 1;
            if (b.data != null)
                return -1;
            return a.name.compareToIgnoreCase(b.name);
        }
    }
}