Android Open Source - mobilepower-android Fragment Layout Controller






From Project

Back to project page mobilepower-android.

License

The source code is released under:

Apache License

If you think the Android project mobilepower-android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2013 Dario Scoppelletti, <http://www.scoppelletti.it/>.
 * /* w  ww . j av a  2  s .co  m*/
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.scoppelletti.mobilepower.app;

import java.util.*;
import android.app.*;
import android.os.*;
import android.support.v4.app.*;
import org.apache.commons.lang3.*;
import org.slf4j.*;
import it.scoppelletti.mobilepower.os.*;

/**
 * Controllore dei frammenti di dettaglio.
 * 
 * <P>Se cambia la configurazione del dispositivo, ad esempio
 * l&rsquo;orientamento, e quindi il layout, Android si limita a visualizzare i
 * soli frammenti inseriti in pannelli ancora esistenti nel nuovo layout mentre
 * gli altri frammenti rimangono invisibili; anche il back stack resta
 * invariato, quindi la pressione del bottone back potrebbe, ad esempio, 
 * rimuovere uno dei frammenti invisibili senza alcuna evidenza dal punto di
 * vista dell&rsquo;interfaccia utente.<BR>
 * La classe {@code FragmentLayoutController}, al ripristino dell&rsquo;istanza
 * dell&rsquo;attivit&agrave;, annulla le transazioni nel back stack fino alla
 * configurazione dei frammenti dell&rsquo;avvio dell&rsquo;applicazione e poi
 * reinserisce le stesse transazioni nel back stack riproducendo le
 * configurazioni dei frammenti come se le transazioni fossero state
 * originariamente eseguite con la configurazione del dispositivo corrente.<BR>
 * La classe {@code FragmentLayoutController} assume che l&rsquo;applicazione
 * implementi le transazioni sui frammenti con una logica di pannelli nel layout
 * organizzati in una gerarchia di livelli di dettaglio nei quali ogni frammento
 * &egrave; inserito nei pannelli disponibili con logica 
 * <ACRONYM TITLE="First In-First Out">FIFO</ACRONYM>.</P>
 * 
 * @since 1.0
 */
public final class FragmentLayoutController {

    /**
     * Stato 
     * {@code it.scoppelletti.mobilepower.app.FragmentLayoutController.panelCount}:
     * Numero di pannelli a disposizione per i frammenti di dettaglio.
     */
    public static final String STATE_PANELCOUNT =
        "it.scoppelletti.mobilepower.app.FragmentLayoutController.panelCount";
    
    private static final Logger myLogger = LoggerFactory.getLogger(
            "FragmentLayout");
    private final Activity myActivity;
    private final int myFrameCount;
    private final int[] myFrameIds;
    
    /**
     * Costruttore.
     * 
     * <P>L&rsquo;attivit&agrave; istanzia un oggetto
     * {@code FragmentLayoutController} all&rsquo;interno del proprio metodo 
     * {@code onCreate} dopo aver costruito la propria vista.</P>
     * 
     * @param activity Attivit&agrave;. 
     * @param frameIds Identificatori dei pannelli a disposizione per i 
     *                 frammenti di dettaglio.
     */
    public FragmentLayoutController(Activity activity, int... frameIds) {
        int frameCount;
        
        if (activity == null) {
            throw new NullPointerException("Argument activity is null.");
        }       
        if (ArrayUtils.isEmpty(frameIds)) {
            throw new NullPointerException("Argument frameIds is null.");
        }
        
        myActivity = activity;
        frameCount = 0;        
        myFrameIds = Arrays.copyOf(frameIds, frameIds.length);
        for (int frameId : myFrameIds) {
            if (myActivity.findViewById(frameId) != null) {
                frameCount++;
            }
        }
        
        if (frameCount == 0) {
            throw new IllegalStateException("No panel found.");
        }
        
        myFrameCount = frameCount;        
    }
    
    /**
     * Restituisce il numero di pannelli a disposizione per i frammenti di
     * dettaglio.
     * 
     * @return Valore.
     */
    public int getFrameCount() {
        return myFrameCount;
    }
    
    /**
     * Salva lo stato dell&rsquo;istanza.
     * 
     * <P>L&rsquo;attivit&agrave; salva lo stato dell&rsquo;istanza
     * {@code FragmentLayoutController} all&rsquo;interno del proprio metodo
     * {@code onSaveInstanceState}.</P>
     * 
     * @param outState Stato da salvare.
     */
    public void onSaveInstanceState(Bundle outState) {
        if (outState == null) {
            throw new NullPointerException("Argument outState is null.");
        }
        
        outState.putInt(FragmentLayoutController.STATE_PANELCOUNT,
                myFrameCount);
    }  
    
    /**
     * Ripristina lo stato dell&rsquo;istanza.
     * 
     * <P>L&rsquo;attivit&agrave; ripristina lo stato dell&rsquo;istanza
     * {@code FragmentLayoutController} all&rsquo;interno del proprio metodo
     * {@code onRestoreInstanceState}.</P>
     * 
     * @param savedInstanceState Stato dell&rsquo;istanza.
     * @param fragmentCollector  Collettore dei frammenti di dettaglio. 
     */
    public void onRestoreInstanceState(Bundle savedInstanceState,
            FragmentLayoutController.FragmentCollector fragmentCollector) {
        int n, oldPanelCount, tnId;
        String tag;
        ActivitySupport activitySupport;        
        FragmentSupport fragment;
        FragmentManager fragmentMgr;
        FragmentLayoutController.BackStackChangedListener backStackListener;
        Queue<FragmentSupport> fragmentQueue;
        Queue<FragmentLayoutController.FragmentEntry> clonedQueue;
        
        if (savedInstanceState == null) {
            throw new NullPointerException(
                    "Argument savedInstanceState is null.");
        }
        if (fragmentCollector == null) {
            throw new NullPointerException(
                    "Argument fragmentCollector is null.");            
        }
        
        if (!(myActivity instanceof ActivitySupport)) {
            myLogger.warn("Activity not implement interface ActivitySupport.");
            return;
        }
                
        oldPanelCount = savedInstanceState.getInt(
                FragmentLayoutController.STATE_PANELCOUNT, 0);
        if (oldPanelCount < 1) {
            myLogger.warn("Unexpected {}={} in saved instance state.",
                    FragmentLayoutController.STATE_PANELCOUNT, oldPanelCount);
            return;
        }
        
        myLogger.debug("{}: current={}, saved instance state={}.",
                new Object[] { FragmentLayoutController.STATE_PANELCOUNT,
                myFrameCount, oldPanelCount });
        if (oldPanelCount == myFrameCount) {
            // Il numero di pannelli non e' cambiato:
            // Il sistema ha gia' ripristinato correttamente i frammenti.
            return;
        }                
        
        fragmentQueue = new ArrayDeque<FragmentSupport>();
        fragmentCollector.collectFragments(fragmentQueue);
        
        // Ad ogni frammento associo il tag con il quale &egrave; stato
        // inserito
        clonedQueue = new ArrayDeque<FragmentLayoutController.FragmentEntry>();
        while (!fragmentQueue.isEmpty()) {
            fragment = fragmentQueue.remove();
            if (fragment == null) {
                myLogger.warn("Ignoring null.");
                continue;
            }
            
            tag = fragment.asFragment().getTag();
            if (StringUtils.isBlank(tag)) {
                myLogger.warn("Ignoring fragment with empty tag.");
                continue;
            }
            
            clonedQueue.offer(new FragmentLayoutController.FragmentEntry(
                    fragment.cloneFragment(), tag));
        }
        
        fragmentQueue = null; // free memory
        
        activitySupport = (ActivitySupport) myActivity;
        fragmentMgr = activitySupport.getSupportFragmentManager();
        
        // Ripristino la configurazione dei frammenti iniziale
        for (n = fragmentMgr.getBackStackEntryCount(); n > 0; n--) {
            fragmentMgr.popBackStack();
        }                       
        
        if (myFrameCount > 1) {
            tnId = arrangeFragments(fragmentMgr, clonedQueue);
        } else {
            tnId = arrangePanel(fragmentMgr, clonedQueue);
        }                                          
        
        if (Build.VERSION.SDK_INT < BuildCompat.VERSION_CODES.HONEYCOMB) {
            return;
        }
        
        // - Android 4.1.2
        // La barra delle azioni non e' correttamente aggiornata forse perche'
        // si assume che non ce ne sia bisogno con transazioni schedulate
        // durante il ripristino dell'attivita' (o magari perche' non e' proprio
        // previsto che si schedulino transazioni durante il ripristino
        // dell'attivita'):
        // Visto che l'esecuzione delle transazioni e' asincrona, devo
        // utilizzare un gestore degli eventi di modifica del back stack che
        // gestisca l&rsquo;ultima transazione che ho schedulato.
        backStackListener =
                new FragmentLayoutController.BackStackChangedListener(
                        myActivity, fragmentMgr, tnId);        
        fragmentMgr.addOnBackStackChangedListener(backStackListener);        
    }
    
    /**
     * Ricostruisce la successione dei frammenti nell&rsquo;unico pannello.
     * 
     * @param  fragmentMgr   Gestore dei frammenti.
     * @param  fragmentQueue Frammenti.
     * @return               Identificatore dell&rsquo;ultimo elemento inserito
     *                       nel back stack. 
     */    
    private int arrangePanel(FragmentManager fragmentMgr,
            Queue<FragmentLayoutController.FragmentEntry> fragmentQueue) {
        int tnId, lastTnId;
        FragmentLayoutController.FragmentEntry entry;
        FragmentTransaction fragmentTn = null;
        
        lastTnId = -1;
        while (!fragmentQueue.isEmpty()) {
            tnId = -1;
            entry = fragmentQueue.remove();
            
            try {
                fragmentTn = fragmentMgr.beginTransaction();
                
                fragmentTn.replace(myFrameIds[0],
                        entry.getFragment().asFragment(), entry.getTag());
                
                fragmentTn.addToBackStack(null);
            } finally {
                if (fragmentTn != null) {
                    tnId = fragmentTn.commit();
                    fragmentTn = null;
                }                 
            }         
            
            if (tnId >= 0) {
                lastTnId = tnId;
            }                           
        }
        
        return lastTnId;
    }
    
    /**
     * Ricostruisce la successione della disposizione dei frammenti nei
     * pannelli.
     * 
     * @param  fragmentMgr   Gestore dei frammenti.
     * @param  fragmentQueue Frammenti.
     * @return               Identificatore dell&rsquo;ultimo elemento inserito
     *                       nel back stack.
     */
    private int arrangeFragments(FragmentManager fragmentMgr,
            Queue<FragmentLayoutController.FragmentEntry> fragmentQueue) {
        int i;
        int frameCount, tnId, lastTnId;
        FragmentLayoutController.FragmentEntry entry;
        FragmentSupport newFragment, oldFragment;
        FragmentLayoutController.FragmentEntry[] frames;
        FragmentTransaction fragmentTn = null;
                
        frameCount = 1;
        frames = new FragmentLayoutController.FragmentEntry[myFrameCount];
        Arrays.fill(frames, null);
        
        lastTnId = -1;
        while (!fragmentQueue.isEmpty()) {
            tnId = -1;
            entry = fragmentQueue.remove();
            
            try {
                fragmentTn = fragmentMgr.beginTransaction();
                
                if (frameCount == myFrameCount) {
                    // Tutti i pannelli sono occupati:
                    // Sposto ogni frammento nel pannello precedente per
                    // liberare l'ultimo.
                    for (i = 0; i < frameCount; i++) {
                        if (frames[i] == null) {
                            // Inizialmente il primo pannello risulta vuoto
                            // anche se in realta' e' occupato dal frammento
                            // principale (non di dettaglio).
                            continue;                            
                        }
                        
                        oldFragment = frames[i].getFragment();
                        newFragment = (i > 0) ? oldFragment.cloneFragment() :
                            null;
                        fragmentTn.remove(oldFragment.asFragment());
                        frames[i] = null;
                        
                        if (newFragment != null) {
                            fragmentTn.replace(myFrameIds[i - 1],
                                    newFragment.asFragment(), entry.getTag());
                            frames[i - 1] =
                                    new FragmentLayoutController.FragmentEntry(
                                            newFragment, entry.getTag());
                        }
                    }
                    
                    frameCount--;
                }
                
                fragmentTn.add(myFrameIds[frameCount],
                        entry.getFragment().asFragment(), entry.getTag());
                frames[frameCount++] = entry; 
                                
                fragmentTn.addToBackStack(null);
            } finally {
                if (fragmentTn != null) {
                    tnId = fragmentTn.commit();
                    fragmentTn = null;
                }
            }
            
            if (tnId >= 0) {
                lastTnId = tnId;
            }            
        }        
        
        return lastTnId;
    }
    
    /**
     * Collettore di frammenti di dettaglio.
     * 
     * @since 1.0.0
     */
    public interface FragmentCollector {
        
        /**
         * Accoda i frammenti di dettaglio da ridisporre nei pannelli a 
         * disposizione.
         * 
         * @param queue Coda.
         */
        void collectFragments(Queue<FragmentSupport> queue);
    }
    
    /**
     * Frammento da reinserire.
     */
    private static final class FragmentEntry {
        private final FragmentSupport myFragment;
        private final String myTag;
        
        /**
         * Costruttore.
         * 
         * @param fragment Frammento.
         * @param tag      Tag.
         */
        FragmentEntry(FragmentSupport fragment, String tag) {            
            myFragment = fragment;
            myTag = tag;
        }
        
        /**
         * Restituisce il frammento.
         * 
         * @return Oggetto.
         */
        FragmentSupport getFragment() {
            return myFragment;
        }
        
        /**
         * Restituisce il tag.
         * 
         * @return Valore.
         */
        String getTag() {
            return myTag;
        }                
    }
    
    /**
     * Gestore della modifica del back-stack.
     */
    private static final class BackStackChangedListener implements
        FragmentManager.OnBackStackChangedListener, Runnable {
        private static final Logger myLogger = LoggerFactory.getLogger(
                "FragmentLayout");                
        private final Activity myActivity;
        private final FragmentManager myFragmentMgr;
        private final int myWaitingForEntryId;
        private final Handler myHandler;
        
        /**
         * Costruttore.
         * 
         * @param activity          Attivit&agrave;.
         * @param fragmentMgr       Gestore dei frammenti.
         * @param waitingForEntryId Identificatore dell&rsquo;elemento atteso.
         */
        BackStackChangedListener(Activity activity,
                FragmentManager fragmentMgr, int waitingForEntryId) {
            myActivity = activity;
            myFragmentMgr = fragmentMgr;
            myWaitingForEntryId = waitingForEntryId;
            myHandler = new Handler();
            myLogger.trace("Waiting for back stack entry {}.",
                    myWaitingForEntryId);
        }
        
        /**
         * Gestisce la modifica del back-stack.
         */
        public void onBackStackChanged() {
            int n, tnId;
            FragmentManager.BackStackEntry entry;
            
            n = myFragmentMgr.getBackStackEntryCount();
            if (n == 0) {
                myLogger.trace("Back stack is empty.");
                return;
            }
            
            entry = myFragmentMgr.getBackStackEntryAt(n - 1);
            tnId = entry.getId();
            myLogger.trace("Back stack entry {} intercepted.", tnId);
            if (tnId == myWaitingForEntryId) {
                myFragmentMgr.removeOnBackStackChangedListener(this);
                myHandler.post(this);                
            }
        }

        /**
         * Esegue l&rsquo;operazione.
         */
        public void run() {
            ActivityCompat.invalidateOptionsMenu(myActivity);
        }
    }   
}




Java Source Code List

it.scoppelletti.mobilepower.app.AboutActivity.java
it.scoppelletti.mobilepower.app.AbstractActivity.java
it.scoppelletti.mobilepower.app.AbstractDialogFragment.java
it.scoppelletti.mobilepower.app.AbstractFragment.java
it.scoppelletti.mobilepower.app.AbstractPreferenceActivity.java
it.scoppelletti.mobilepower.app.ActionBarCompat.java
it.scoppelletti.mobilepower.app.ActionBarSupport.java
it.scoppelletti.mobilepower.app.ActivitySupport.java
it.scoppelletti.mobilepower.app.AppUtils.java
it.scoppelletti.mobilepower.app.CommonMenuFragment.java
it.scoppelletti.mobilepower.app.ConfirmDialogFragment.java
it.scoppelletti.mobilepower.app.DatePickerDialogFragment.java
it.scoppelletti.mobilepower.app.FragmentLayoutController.java
it.scoppelletti.mobilepower.app.FragmentSupport.java
it.scoppelletti.mobilepower.app.HelpActivity.java
it.scoppelletti.mobilepower.app.HelpDialogFragment.java
it.scoppelletti.mobilepower.app.MarketTagHandler.java
it.scoppelletti.mobilepower.app.ProgressDialogFragment.java
it.scoppelletti.mobilepower.app.ReleaseNoteActivity.java
it.scoppelletti.mobilepower.app.bluetooth.ActivityBTManager.java
it.scoppelletti.mobilepower.app.data.DatabaseConnectionManager.java
it.scoppelletti.mobilepower.app.data.DatabaseUpgradeTask.java
it.scoppelletti.mobilepower.app.security.ActivityLicenseClient.java
it.scoppelletti.mobilepower.app.security.LicenseBuyDialogFragment.java
it.scoppelletti.mobilepower.app.security.LicenseDemoDialogFragment.java
it.scoppelletti.mobilepower.app.security.LicenseRetryDialogFragment.java
it.scoppelletti.mobilepower.bluetooth.BTManager.java
it.scoppelletti.mobilepower.bluetooth.BTTask.java
it.scoppelletti.mobilepower.bluetooth.OnBTListener.java
it.scoppelletti.mobilepower.data.DatabaseUpgrader.java
it.scoppelletti.mobilepower.data.OnDatabaseConnectionListener.java
it.scoppelletti.mobilepower.graphics.GraphicTools.java
it.scoppelletti.mobilepower.graphics.SizeF.java
it.scoppelletti.mobilepower.io.IOTools.java
it.scoppelletti.mobilepower.media.DefaultOnScanCompletedListener.java
it.scoppelletti.mobilepower.os.AbstractAsyncTask.java
it.scoppelletti.mobilepower.os.AsyncTaskController.java
it.scoppelletti.mobilepower.os.AsyncTaskHost.java
it.scoppelletti.mobilepower.os.BuildCompat.java
it.scoppelletti.mobilepower.preference.AbstractDialogPreference.java
it.scoppelletti.mobilepower.preference.BTDevicePreference.java
it.scoppelletti.mobilepower.preference.ColorPreference.java
it.scoppelletti.mobilepower.preference.EditTextPreferenceEx.java
it.scoppelletti.mobilepower.preference.IntegerSpinnerPreference.java
it.scoppelletti.mobilepower.preference.PreferenceSavedState.java
it.scoppelletti.mobilepower.preference.SeekBarPreference.java
it.scoppelletti.mobilepower.provider.ContactsContractCompat.java
it.scoppelletti.mobilepower.reflect.MemberCache.java
it.scoppelletti.mobilepower.security.LicenseClient.java
it.scoppelletti.mobilepower.types.SimpleDate.java
it.scoppelletti.mobilepower.types.StringTools.java
it.scoppelletti.mobilepower.types.TimeTools.java
it.scoppelletti.mobilepower.types.ValueTools.java
it.scoppelletti.mobilepower.types.ValueType.java
it.scoppelletti.mobilepower.view.ViewSavedState.java
it.scoppelletti.mobilepower.widget.CompoundControl.java
it.scoppelletti.mobilepower.widget.DateControl.java
it.scoppelletti.mobilepower.widget.ImageButtonCompat.java
it.scoppelletti.mobilepower.widget.IntegerSpinner.java
it.scoppelletti.mobilepower.widget.IntegerTextWatcher.java
it.scoppelletti.mobilepower.widget.WidgetTools.java