JavascriptProva

venerdì 23 dicembre 2016

VIew Pager e fragments con adapter

Creo un fragment.
public class Frammento extends Fragment {

    OnFragmentInteractionListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento,container,false);
        return v;
    }

    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof com.antonello.frammenti.OnFragmentInteractionListener){
            mListener=(OnFragmentInteractionListener)context;
        }
    }

    public interface OnFragmentInteractionListener{
        public void onFragmentInteraction();
    }

}
Questo è lo scheletro essenziale per creare un fragment.
Ora voglio aggiungervi un Button programmaticamente.
public class Frammento extends Fragment {

    Button button;
    LinearLayout mainLayout;
    OnFragmentInteractionListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento,container,false);
        button=new Button(v.getContext());
        mainLayout=(LinearLayout)v.findViewById(R.id.mainLayout);
        mainLayout.addView(button);
        return v;
    }

    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof com.antonello.frammenti.OnFragmentInteractionListener){
            mListener=(OnFragmentInteractionListener)context;
        }
    }

    public interface OnFragmentInteractionListener{
        public void onFragmentInteraction();
    }

}
Ecco, ho aggiunto un button senza alcun listener.
Ora voglio vedere se "funziona".

Devo caricare il fragment nel Layout principale.

Provo con questo codice, se lo ricordo abbastanza bene:
public class MainActivity extends AppCompatActivity implements FragmentBarra.OnFragmentInteractionListener{

    Helper helper;
    Fragment myFrag;
    FragmentManager fragmentManager=getSupportFragmentManager();
    FragmentTransaction transaction;
    


    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myFrag=new Frammento();
        transaction=fragmentManager.beginTransaction();
        transaction.add(R.id.barra,myFrag,"myfrag");
        transaction.commit();






    }

    @Override
    public void onFragmentInteraction() {

    }




}
Il button c'è.
Ora dovrei collegare il ViewPager con un adapter.
Nella fattispecie si usa il FragmentPagerAdapter, che devo estendere.
Ci provo...

    public class Adapter extends FragmentPagerAdapter{

        public Adapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return null;
        }

        @Override
        public int getCount() {
            return 0;
        }
    }
Già scrivendo l'estensione della classe Android Studio richiede l'implementazione dei metodi e la creazione del costruttore in automatico, per cui la cosa non presenta molti problemi.
Costruisco direttamente una nuova istanza del Fragment da inserire nell'altro LinearLayout, passandoci il numero di posizione attribuito dall'adapter:
    public class Adapter extends FragmentPagerAdapter{

        public Adapter(FragmentManager fm) {

            super(fm);
        }

        @Override
        public Fragment getItem(int position) {

            frag3=new Frammento3();
            Bundle args=new Bundle();
            args.putInt("num",position);
            frag3.setArguments(args);
            return frag3;
        }

        @Override
        public int getCount() {

            return 2;
        }
    }
Nel codice del fragment ho una variabile int che accoglie nel metodo onCreate il numero attribuito come parametro, ossia position.
public class Frammento3 extends Fragment {

    OnFragmentInteractionListener mListener;

    int numero;

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        numero=getArguments().getInt("num");
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento3,container,false);
        TextView textView=(TextView)v.findViewById(R.id.textView2);
        textView.setText(numero+"");
        return v;
    }

    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof OnFragmentInteractionListener){
            mListener=(OnFragmentInteractionListener)context;
        }
    }
}
Senza fare quella costruzione con newInstance, della quale non ho, sinceramente, capito lo scopo.

mercoledì 21 dicembre 2016

Codice per il viewPager.

Mi salvo un codice e torno ad esercitarmi su di esso...
public class MainActivity extends AppCompatActivity implements FragmentBarra.OnFragmentInteractionListener{

    Helper helper;
    Frammento myFrag;
    FragmentBarra fragmentBarra;
    PagerAdapter adapter;
    ViewPager viewPager;
    ArrayList lista;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        lista=new ArrayList();
        lista.add("Gongolo");
        lista.add("Mammolo");
        lista.add("Eolo");
        lista.add("Pisolo");
        lista.add("Brontolo");
        lista.add("Dotto");
        lista.add("Cucciolo");
        helper=new Helper(this);

        viewPager=(ViewPager)findViewById(R.id.vpdisplay);
        adapter=new ScreenSlidePagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);

        fragmentBarra = new FragmentBarra();
        FragmentManager fragmentManager=getSupportFragmentManager();
        FragmentTransaction transaction=fragmentManager.beginTransaction();
        transaction.add(R.id.barra,fragmentBarra);

        /*helper.save("Pinco");
        helper.save("Lappo");
        helper.save("Bobbo");*/





    }

    @Override
    public void onFragmentInteraction() {
        myFrag.getView().setBackgroundColor(Color.GREEN);
    }

    public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter{

        public ScreenSlidePagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {

            return MioFrammento.newInstance(position,lista);
        }

        @Override
        public int getCount() {
            return 10;
        }
    }

    public static class MioFrammento extends Fragment{
        int numero;
        Serializable lista;
        public static MioFrammento newInstance(int num, ArrayList lista){
            MioFrammento f=new MioFrammento();
           0
            Bundle args=new Bundle();
            args.putInt("num",num);
            args.putSerializable("lista",lista);
            f.setArguments(args);
            return f;
        }

        @Override
        public void onCreate(Bundle savedInstanceState){
            super.onCreate(savedInstanceState);
            numero= getArguments().getInt("num");
            lista=getArguments().getSerializable("lista");
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState){
            View v=inflater.inflate(R.layout.mio_frammento,container,false);
            TextView textView=(TextView)v.findViewById(R.id.textView);
            textView.setText(((ArrayList)lista).get(numero));
            return v;
        }
    }


}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#AAFFFF"
    android:orientation="horizontal"
    android:id="@+id/container"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <LinearLayout
        android:id="@+id/barra"
        android:orientation="horizontal"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/vpdisplay"
        android:layout_height="match_parent"
        android:layout_width="0dp"
        android:layout_weight="4">

    </android.support.v4.view.ViewPager>

</LinearLayout> 

lunedì 19 dicembre 2016

ViewPager e relativo codice

Con il ViewPager bisogna sempre creare l'adapter.
Sembra che PagerAdapter sia un antenato di ScreenSlidePagerAdapter.

Il codice è infatti qualcosa del tipo
PagerAdapter pagerAdapter=new ScreenSlidePagerAdapter(getSupportFragmentManager())
Ossia il costruttore di ScreenSlidePagerAdapter ha bisogno di un parametro di tipo FragmentManager o derivato.
Una volta istanziato il PagerAdapter, e istanziato il ViewPager, si azzecca l'adapter al ViewPager, ma come si aggiungono pagine al PagerAdapter?

In realtà ho commesso un errore: ScreenSlidePagerAdapter è una classe creata ex novo, che estende FragmentStatePagerAdapter.
Vediamo il codice per la creazione di ScreenSlidePagerAdapter che estende FragmentStatePagerAdapter...
Ecco, ha due proprietà: una accetta come parametro un int posizione e restituisce un Fragment, l'altra imposta il numero di elementi.
    private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
        public ScreenSlidePagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            switch(position){
                case 0:
                    return new Frammento();

                case 1:
                    return new Frammento2();

                case 2:
                    return new Frammento3();

                default:break;

            }
            return null;
        }

        @Override
        public int getCount() {
            return NUM_PAGES;
        }
    }

Lettura di un database tramite i fragments

Per il momento creo un database o verifico se un database sia già stato creato.
Un database c'è, ed è presente su storage anziché sulla cartella inaccessibile. Credo che questa sia una soluzione più conveniente, anche se espone il database a un maggior rischio, per la possibilità di trasferirlo facilmente mediante un semplice meccanismo di copia-incolla anche su dispositivi non rootati.

Ripristino il codice per aggiungere voci al database.
public class MainActivity extends AppCompatActivity implements Frammento.OnFragmentInteractionListener{

    Helper helper;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        
        helper.save("Mario");
        helper.save("Pippo");
        helper.save("Mimmo");

,,,,,
Ora mi occupo di caricare un fragment.
Per prima cosa ho creato il codice del fragment, con tanto di creazione dell'interfaccia per inviare messaggi all'activity:
public class Frammento3 extends Fragment {

    OnFragmentInteractionListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento3,container,false);
        return v;
    }

    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof OnFragmentInteractionListener){
            mListener=(OnFragmentInteractionListener)context;
        }
    }
}
Quindi ho creato il codice dell'activity principale:
public class MainActivity extends AppCompatActivity implements Frammento.OnFragmentInteractionListener{

    Helper helper;
    Frammento myFrag;
    FragmentManager fragmentManager=getSupportFragmentManager();
    FragmentTransaction transaction=fragmentManager.beginTransaction();

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);

        /*helper.save("Mario");
        helper.save("Pippo");
        helper.save("Mimmo");*/

        myFrag=new Frammento();
        transaction.add(R.id.display,myFrag,"frammento");
        transaction.commit();



    }

    @Override
    public void onFragmentInteraction() {

    }
}
(ho oscurato i comandi che avevo usato in precedenza per la scrittura del database).

Ottengo il caricamento del frammento.

Ora devo fare in modo che il fragment legga il database disponendo i dati che vi trova in qualche view...

Fatto.
Viene creata una TextView per ogni record appartenente al recordset.
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=(View)inflater.inflate(R.layout.frammento,container,false);
        LinearLayout layout=(LinearLayout)v.findViewById(R.id.frammento);
        helper=new Helper(v.getContext());
        Cursor c=helper.query();
        do{
            testo=new TextView(v.getContext());
            testo.setText(c.getString(c.getColumnIndex("nome")));
            layout.addView(testo);
        }while(c.moveToNext());
        return v;
    }
E funziona.

sabato 17 dicembre 2016

Tornando a Giobe...

Ricominciamo dalla firma di Zbikovsky...
C:\Arch-Lab\LAVORO~1>debug es1.xxx
-d
17BC:0100  4D 5A 16 01 02 00 00 00-20 00 00 00 FF FF 00 00   MZ...... .......
17BC:0110  00 00 00 00 00 01 00 00-1E 00 00 00 01 00 00 00   ................
17BC:0120  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:0130  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:0140  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:0150  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:0160  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:0170  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
Andando indietro vado al PSP:
-d 17bc:0000
17BC:0000  CD 20 FF 9F 00 9A EE FE-1D F0 4F 03 20 12 8A 03   . ........O. ...
17BC:0010  20 12 17 03 20 12 2C 07-01 01 01 00 02 FF FF FF    ... .,.........
17BC:0020  FF FF FF FF FF FF FF FF-FF FF FF FF A0 11 4E 01   ..............N.
17BC:0030  E0 16 14 00 18 00 BC 17-FF FF FF FF 00 00 00 00   ................
17BC:0040  05 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:0050  CD 21 CB 00 00 00 00 00-00 00 00 00 00 20 20 20   .!...........
17BC:0060  20 20 20 20 20 20 20 20-00 00 00 00 00 20 20 20           .....
17BC:0070  20 20 20 20 20 20 20 20-00 00 00 00 00 00 00 00           ........
-d
17BC:0080  00 0D 65 73 31 2E 78 78-78 0D 65 78 65 0D 20 49   ..es1.xxx.exe. I
17BC:0090  35 20 44 31 20 50 33 33-30 20 54 33 0D 74 2E 65   5 D1 P330 T3.t.e
17BC:00A0  78 65 0D 00 00 00 00 00-00 00 00 00 00 00 00 00   xe..............
17BC:00B0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:00C0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:00D0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:00E0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
17BC:00F0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
-

mercoledì 14 dicembre 2016

Gestione dei frammenti su un'activity, con codice per l'espansione di un fragment.

Non ho lasciato nessun promemoria scritto di come caricare più fragments in un'activity scambiandone uno su comando dell'altro.
Ecco:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#AAFFFF"
    android:orientation="horizontal"
    android:id="@+id/container"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <LinearLayout
        android:id="@+id/barra"
        android:orientation="horizontal"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2"/>

    <LinearLayout
        android:id="@+id/display"
        android:orientation="horizontal"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"/>


</LinearLayout> 
public class MainActivity extends AppCompatActivity implements Frammento.OnFragmentInteractionListener{

    Frammento myFrag;
    FragmentManager fragmentManager;
    FragmentTransaction transaction;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myFrag=new Frammento();
        fragmentManager=getSupportFragmentManager();
        transaction=fragmentManager.beginTransaction();
        transaction.add(R.id.barra,myFrag,"barra");
        transaction.commit();

        Frammento2 frag2=new Frammento2();
        transaction=fragmentManager.beginTransaction();
        transaction.add(R.id.display,frag2,"display");
        transaction.commit();

    }

    public void onFragmentInteraction(){
        Frammento2 frammento2=(Frammento2)fragmentManager.findFragmentByTag("display");
        Button bottone=new Button(getApplicationContext());
        LinearLayout mainLayout=(LinearLayout)frammento2.getView().findViewById(R.id.frammento2);
        mainLayout.addView(bottone);
    }

}




Fragments:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:background="#FFFFFF"
    android:id="@+id/frammento">


    <Button
        android:text="Button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button2" />
</ScrollView> 
public class Frammento extends Fragment {

    OnFragmentInteractionListener mListener;
    Button button;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.frammento, container, false);
        button=(Button)rootView.findViewById(R.id.button2);
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                mListener.onFragmentInteraction();
            }
        });
        return rootView;
    }

    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof OnFragmentInteractionListener){
            mListener=(OnFragmentInteractionListener)context;
        }
    }

    public interface OnFragmentInteractionListener{
        public void onFragmentInteraction();
    }
}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="4"
    android:id="@+id/frammento2"
    android:background="#FAA">

    <TextView
        android:text="SECONDO FRAMMENTO"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView" />
</LinearLayout> 
public class Frammento2 extends Fragment {

    OnFragmentInteractionListener mListener;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento2,container,false);
        return v;
    }

    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof OnFragmentInteractionListener){
            mListener=(OnFragmentInteractionListener)context;
        }
        System.out.println("FRAMMENTO 2 ONATTACH");

    }


}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:background="#AFA"
    android:layout_weight="4">



    <TextView
        android:text="TERZO FRAMMENTO"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView2" />

    <Button
        android:text="Button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button4" />
</LinearLayout> 
public class Frammento3 extends Fragment {

    OnFragmentInteractionListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento3,container,false);
        return v;
    }

    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof OnFragmentInteractionListener){
            mListener=(OnFragmentInteractionListener)context;
        }
    }
}

Spinner (ripasso)

Ho ripassato la creazione di uno Spinner. Ed ecco il codice:
        <Spinner
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:id="@+id/spinner"
            android:background="#FFFFFF"
            android:layout_weight="1" />
public class MainActivity extends AppCompatActivity {

    ArrayAdapter adapter;
    Spinner spinner;
    List esami;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        esami=new ArrayList();
        esami.add("RBC");
        esami.add("Hb");
        esami.add("MCV");
        adapter=new ArrayAdapter(this,android.R.layout.simple_spinner_dropdown_item,esami);
        spinner=(Spinner)findViewById(R.id.spinner);
        spinner.setAdapter(adapter);


    }

}

Sliding Up Panel

Mi salvo il codice per lo Sliding Panel.
Questo è il link
<?xml version="1.0" encoding="utf-8"?>
<com.sothree.slidinguppanel.SlidingUpPanelLayout
    xmlns:sothree="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="top"
    sothree:umanoPanelHeight="20dp"
    sothree:umanoShadowHeight="10dp">
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:background="#FFAAAA"
        android:layout_height="match_parent"
        android:layout_width="match_parent">

        <TextView
            android:text="Base"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textView4"
            android:layout_weight="1" />


    </LinearLayout>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#AAFFFF"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
    <TextView
        android:text="Pannello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textView5"
        android:layout_weight="1" />




</LinearLayout>

</com.sothree.slidinguppanel.SlidingUpPanelLayout> 

lunedì 12 dicembre 2016

Codice per slide con barra laterale fissa

Mi salvo il codice:
public class MainActivity extends AppCompatActivity {

    Helper helper;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        helper=new Helper(this);

        /*helper.save("Mario");
        helper.save("Pasquale");*/

        helper.saveData(1,"Primo");

    }

    class Helper extends SQLiteOpenHelper{

        public Helper(Context context) {
            super(context, "mioDatabase.db", null, 1);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("create table tblNomi(_id integer primary key, nome text)");
            db.execSQL("create table tblDati(_id integer primary key, extKey integer, dato text)");
        }

        public void save(String nome){
            SQLiteDatabase db=this.getWritableDatabase();
            ContentValues values=new ContentValues();
            values.put("nome",nome);
            db.insert("tblNomi",null,values);

        }

        public void saveData(int id, String dato){
            SQLiteDatabase db=this.getWritableDatabase();
            ContentValues values=new ContentValues();
            values.put("extKey",id);
            values.put("dato",dato);
            db.insert("tblDati",null,values);
        }

        public Cursor query(int id){
            SQLiteDatabase db=this.getWritableDatabase();
            Cursor c=db.rawQuery("select * from tblNomi where _id="+id, null);
            c.moveToFirst();
            return c;
        }






        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        }
    }

}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <ImageView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        app:srcCompat="@mipmap/ic_launcher"
        android:id="@+id/imageView"
        android:layout_weight="1" />

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="5"/>


</LinearLayout> 

domenica 11 dicembre 2016

Slider per schermate

C'è un Adapter che dovrebbe funzionare come l'adapter per le ListView.
In questo codice, sarebbe questo:
    private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {

        public ScreenSlidePagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return new Frammento();
        }

        @Override
        public int getCount() {
            return NUM_PAGES;
        }
    }
Il suo costruttore è questo:
        public ScreenSlidePagerAdapter(FragmentManager fm) {
            super(fm);
        }
e accetta come parametro FragmentManager.

Come un ArrayAdapter viene "abbinato" a una ListView, così questo Adapter andrebbe "abbinato" a un controllo che fa scorrere le schermate...

Ecco: istanzio un Adapter:
 mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
e quindi lo "azzecco" al ViewPager:
mPager.setAdapter(mPagerAdapter);
Ecco la sequenza completa: prendo il ViewPager dal file XML, istanzio un'istanza dell'Adapter e poi la azzecco al ViewPager:
        mPager = (ViewPager) findViewById(R.id.pager);
        mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
        mPager.setAdapter(mPagerAdapter);
Ecco, il codice completo è questo, e funziona:
public class MainActivity extends AppCompatActivity {

    private static final int NUM_PAGES = 3;


    private ViewPager mPager;


    private PagerAdapter mPagerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

 
        mPager = (ViewPager) findViewById(R.id.pager);
        mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
        mPager.setAdapter(mPagerAdapter);
    }


    private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
        public ScreenSlidePagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            switch(position){
                case 0:
                    return new Frammento();

                case 1:
                    return new Frammento2();

                case 2:
                    return new Frammento3();

                default:break;

            }
            return null;
        }

        @Override
        public int getCount() {
            return NUM_PAGES;
        }
    }
}

mercoledì 7 dicembre 2016

Messaggio dal fragment alla main activity

Esercizio: messaggio del fragment verso l'Activity:

metto un Button nel Frammento 1:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#AADDFF">

    <Button
        android:text="Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/button" />
</RelativeLayout> 
Nel codice Java del Frammento devo creare un'interfaccia:
    public interface OnFragmentEventListener{
        public void evento();
    }
Creo una variabile del tipo di questa interfaccia:
public class Frammento extends Fragment {

    OnFragmentEventListener mCallback;

......
Nell'evento onAttach identifico l'Activity cui il fragment viene "attaccato" come un'istanza dell'interfaccia:
    @Override
    public void onAttach(Context context){
        super.onAttach(context);
        if(context instanceof AppCompatActivity){
            mCallback=(OnFragmentEventListener)context;
        }
    }
E nell'evento onCreateView attribuisco al button il listener e dico di far eseguire nella variabile interfaccia il metodo che questa implementa:
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento,container,false);
        button=(Button)v.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                mCallback.evento();
            }
        });
        return v;
    }
E funziona: quando io clicco il button presente sul Frammento 1 viene eseguito il metodo come implementato dall'Activity Main:
(codice della Main Activity:)
public class MainActivity extends AppCompatActivity implements Frammento.OnFragmentEventListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void evento(){
        System.out.println("EVENTO RICEVUTO");
    }


}

Creazione di due fragments mediante xml.

Esercizio di creazione di due fragments messi in un'Activity per mezzo del codice xml.

Creo i fragments: una classe Java e un file XML.

Frammento.java:
public class Frammento extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento,container,false);
        return v;
    }
}


frammento.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#AADDFF">

    <Button
        android:text="Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/button" />
</RelativeLayout> 



Frammento2.java:
public class Frammento2 extends Fragment{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        View v=inflater.inflate(R.layout.frammento2,container, false);
        return v;
    }
}


frammento2.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFAA">

    <TextView
        android:text="Questo è il frammento 2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView" />
</LinearLayout> 



MainActivity.java:
public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }


}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <fragment android:name="com.antonello.laboratorionuovo2.Frammento"
        android:layout_height="match_parent"
        android:layout_width="0dp"
        android:id="@+id/frammento"
        android:layout_weight="1"/>

    <fragment android:name="com.antonello.laboratorionuovo2.Frammento2"
        android:layout_height="match_parent"
        android:layout_width="0dp"
        android:id="@+id/frammento2"
        android:layout_weight="1"/>

</LinearLayout> 


E così funziona perfettamente.

lunedì 5 dicembre 2016

DatePicker

Come si usa un datePicker?
A quanto pare la chiave è lo showDialog.
public class MainActivity extends AppCompatActivity {


    Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                showDialog(0);
            }
        });
    }
}
Fin qui va bene...

Ora c'è la funzione "di quando si crea una finestra di dialogo", ossia onCreateDialog, di classe Dialog e con un parametro int.

    @Override
    public Dialog onCreateDialog(int id){
        return new DatePickerDialog(this,.........)
    }
E ora vediamo con calma i parametri del costruttore...

Il primo, this, è il context, ordinaria amministrazione;
Il secondo è un listener, OnDateSetListener, che sarebbe l'ascoltatore di quando viene settata la data.
Poi ci sono tre parametri di tipo int che dobbiamo fornire noi.

Ci provo.
Ecco la prima funzione:
    @Override
    public Dialog onCreateDialog(int id){
        return new DatePickerDialog(this, onDateSetListener,anno, mese, giorno);
    }
Ed ecco il listener (secondo parametro della funzione):
    DatePickerDialog.OnDateSetListener onDateSetListener= new DatePickerDialog.OnDateSetListener() {
        @Override
        public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
            anno=year;
            mese=month;
            giorno=dayOfMonth;
        }
    };

domenica 4 dicembre 2016

Codice esercizio gestione suonerie

Mi salvo il codice sulla gestione delle suonerie...
public class MainActivity extends AppCompatActivity {

    Uri ringtoneUri;
    Button button;
    SharedPreferences SP;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button=(Button)findViewById(R.id.button);
        SP=getApplicationContext().getSharedPreferences("settings",MODE_PRIVATE);




        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                String strSuoneria=SP.getString("suoneria","");
                if(TextUtils.isEmpty(strSuoneria)){
                    ringtoneUri=null;

                }
                else{
                    ringtoneUri=Uri.parse(strSuoneria);
                }

                System.out.println("Stringa pescata da SharedPreferences: "+strSuoneria);
                if(ringtoneUri!=null)System.out.println("ringtoneUri selezionata: "+ringtoneUri.toString());



                Intent intent=new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,RingtoneManager.TYPE_NOTIFICATION);

                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,ringtoneUri);
                startActivityForResult(intent,0);
            }
        });


    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        if(resultCode==RESULT_OK) {
            Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
            String s;
            if(uri==null){
                s="";
            }
            else{
                s=uri.toString();
            }
            System.out.println("Stringa memorizzata in SP: "+s);
            SharedPreferences.Editor editor = SP.edit();
            editor.putString("suoneria", s);
            editor.commit();
        }
    }
}

in modo da usare il programma per fare dell'altro.

Impostazione delle suonerie

Raccogliamo le idee...

Quando viene scelta una suoneria, viene scelto un URI che viene attribuito alla variabile uri, la quale viene quindi memorizzata in SP.
Se viene fatta la scelta "Nessuna suoneria", non c'è nessun URI, per cui la variabile uri è nulla, e il tentativo di salvarla in SP genera un errore.

Ho ovviato così, facendo in modo che se URI è assente e uri è quindi nulla venga salvata in SP una stringa vuota.
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        if(resultCode==RESULT_OK) {
            Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
            String s;
            if(uri==null){
                s="";
            }
            else{
                s=uri.toString();
            }
            System.out.println("Stringa memorizzata in SP: "+s);
            SharedPreferences.Editor editor = SP.edit();
            editor.putString("suoneria", s);
            editor.commit();
        }
    }
Ora devo andare a vedere l'altro versante, ossia il richiamo della suoneria da SP.
Se trovo una stringa non vuota, prendo questa come nome della suoneria, creando il ringtoneUri.
Se trovo una stringa vuota, invece, non devo mettere in azione la suoneria di default, bensì impostare ringtoneUri a null.
                String strSuoneria=SP.getString("suoneria","");
                if(TextUtils.isEmpty(strSuoneria)){
                    ringtoneUri=null;

                }
                else{
                    ringtoneUri=Uri.parse(strSuoneria);
                }

                System.out.println("Stringa pescata da SharedPreferences: "+strSuoneria);
                if(ringtoneUri!=null)System.out.println("ringtoneUri selezionata: "+ringtoneUri.toString());
Così dovrebbe funzionare, con suoneria assente e selezione di "Nessuna suoneria", in caso di impostazione di "nessuna suoneria".

Il codice che recupera ringtoneUri e quello che chiama l'activity della scelta suonerie devono essere messi insieme, perché il primo si deve ripetere ogni volta, mentre se viene lasciato in onCreate si esegue solo all'apertura dell'Activity.

Salvataggio grezzo di un codice sulle date

public class MainActivity extends AppCompatActivity {

    SimpleDateFormat sdf;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,8,5);


        int differenza=scuola.getYear()-nascita.getYear();
        if(nascita.getMonth()>scuola.getMonth() ||
                (nascita.getMonth()==scuola.getMonth() && nascita.getDate()>scuola.getDate())){
            differenza--;
        }
        System.out.println(differenza);






    }

    Date dateFromString(String stringa){
        Date date=null;
        stringa=stringa.replace('/',' ');
        stringa=stringa.replace('-',' ');



        int posizione=stringa.length()-stringa.lastIndexOf(" ");

        if(posizione!=5 && posizione!=3)return null;

        if(posizione==5){
            sdf=new SimpleDateFormat("dd M yyyy");
        }
        if(posizione==3){
            sdf=new SimpleDateFormat("dd M yy");
        }

        try {
            sdf.setLenient(false);
            date = sdf.parse(stringa);

        } catch (ParseException e) {
            return null;
        }
        return date;
    }
}

sabato 3 dicembre 2016

Esercizi di calcoli con le date

Stando a quello che ho capito, una data può essere scritta sotto forma di Stringa o sotto forma di Date.
Date può prendere come parametro un numero Long che rappresenta le date a partire dall'Epoch.
L'intermediario fra Date e Stringa è SimpleDateFormat, che usa il metodo parse() per andare da Stringa a Date, e il metodo format() per andare da Date a Stringa.

Facciamo un po' di esercizi in tal senso...
Converto una Stringa in un Date e poi faccio l'inverso.
        String stringa=("5/9/1961");
        Date date=null;

        DateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");

        try {
            date=sdf.parse(stringa);
            System.out.println(date.toString());
        } catch (ParseException e) {
            e.printStackTrace();
        }

        String nuovaStringa=null;

        nuovaStringa=sdf.format(date);

        System.out.println(nuovaStringa);
12-03 10:17:10.433 12831-12831/? I/System.out: Tue Sep 05 00:00:00 GMT+00:00 1961
12-03 10:17:10.433 12831-12831/? I/System.out: 05/09/1961


Quello che ho capito è che la reale misura del tempo è in un tipo Long, che rappresenta il numero di millisecondi dall'Epoch.
Come si estrapolano i millisecondi dall'entità Date?
Ho aggiunto una riga:
        String stringa=("5/9/1961");
        Date date=null;

        DateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");

        try {
            date=sdf.parse(stringa);
            System.out.println(date.toString());
        } catch (ParseException e) {
            e.printStackTrace();
        }

        String nuovaStringa=null;

        nuovaStringa=sdf.format(date);

        System.out.println(nuovaStringa);

        System.out.println(date.getTime());
12-03 11:25:32.368 13022-13022/? I/System.out: Tue Sep 05 00:00:00 GMT+00:00 1961
12-03 11:25:32.368 13022-13022/? I/System.out: 05/09/1961
12-03 11:25:32.368 13022-13022/? I/System.out: -262656000000
Mi sembra perfetto.
Ora, posso ottenere giorno mese e anno separatamente da Date?
Ho provato
        String stringa=("5/9/1961");
        Date date=null;

        DateFormat sdf=new SimpleDateFormat("dd/MM/yyyy");

        try {
            date=sdf.parse(stringa);
            System.out.println(date.toString());
        } catch (ParseException e) {
            e.printStackTrace();
        }


        System.out.println("weekday "+date.getDate());
        System.out.println("day "+date.getDay());
        System.out.println("month "+date.getMonth());
        System.out.println("year "+date.getYear());
ottenendo
12-03 14:26:46.771 13504-13504/? I/System.out: Tue Sep 05 00:00:00 GMT+00:00 1961
12-03 14:26:46.771 13504-13504/? I/System.out: weekday 5
12-03 14:26:46.771 13504-13504/? I/System.out: day 2
12-03 14:26:46.794 13504-13504/? I/System.out: month 8
12-03 14:26:46.794 13504-13504/? I/System.out: year 61
e sembra che proceda tutto bene. Il mese è espresso partendo da zero, però...
SimpleDateFormat serve solo per la conversione da e a stringa.
L'unico problema è che questi metodi sono deprecati, non so perché, e come oggetto Date si consiglia adesso di usare Calendar.
Saranno deprecati ma funzionano.
Dunque posso fare tutto con l'oggetto di classe Date...

Bene. Cerco di fare un'operazione con la sottrazione di due date.
Per fare le operazioni devo ridurre tutto a millisecondi.
Posso ottenere il tempo in millisecondi mediante la funzione Date.getTime().
Ci provo...

Sottraggo 1 ottobre 1966 da 5 settembre 1961.
Un momento!
Come ho estratto giorno, mese e anno da un oggetto di classe Date, devo essere in grado di settare questi valori.
Ricordo dei metodi precisi, anch'essi deprecati.
Si può fare sicuramente da costruttore:
        Date date1=new Date(1966,10,1);

        Date date2=new Date(1961,9,5);

        System.out.println(date1.getMonth());

        System.out.println(date2.getMonth());
12-03 14:43:46.207 13670-13670/? I/System.out: 10
12-03 14:43:46.207 13670-13670/? I/System.out: 9
Devo regolare i conti con il mese.
Per capire qualcosa, faccio una prova: immetto come mese nel costruttore di Date il numero 12, che, se il conto dei mesi inizia da zero, dovrebbe darmi un messaggio di errore in quanto il mese di Dicembre dovrebbe essere rappresentato con 11, e 12 eccederebbe il numero dei mesi.
        Date date1=new Date(1966,12,1);

        Date date2=new Date(1961,9,5);

        System.out.println(date1.getMonth());


        System.out.println(date2.getMonth());
Ed ecco:
12-03 14:50:40.126 13911-13911/? I/System.out: 0
12-03 14:50:40.126 13911-13911/? I/System.out: 9
Ottengo 0, che dovrebbe essere Gennaio...
Mi viene un dubbio: non mi avrà aggiunto un anno in più?
Prova:
        Date date1=new Date(1966,12,1);

        Date date2=new Date(1961,9,5);

        System.out.println(date1.getMonth());
        System.out.println(date1.getYear());

        System.out.println(date2.getMonth());
12-03 14:52:45.347 13995-13995/? I/System.out: 0
12-03 14:52:45.347 13995-13995/? I/System.out: 1967
12-03 14:52:45.347 13995-13995/? I/System.out: 9
Infatti... mi ha aggiunto un anno in più!
Oltre che da costruttore, vediamo se si può impostare i valori di giorno, mese e anno in un oggetto di classe Date.

        Date date1=new Date();

        date1.setDate(5);
        date1.setMonth(8);
        date1.setYear(1961);

        System.out.println(date1);
12-03 14:56:08.224 14083-14083/? I/System.out: Thu Sep 05 14:56:08 GMT+00:00 3861
Perfetto!

. Ora proviamo a sottrarre le due date che avevo detto prima: la data del mio primo giorno di scuola elementare dalla data di nascita.
        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,9,1);

        Long milliDiff=scuola.getTime()-nascita.getTime();
        System.out.println(milliDiff);
Converto in millisecondi e faccio la sottrazione.
Ecco l'output:
12-03 14:59:47.952 14170-14170/? I/System.out: 160012800000
Ma non posso sapere a quanto tempo corrisponde quest'accozzaglia informe di millisecondi. Come faccio?

Posso andare per divisioni successive...

diviso 1000, diviso 3600 diviso 24 e ottengo i giorni... poi ci addentriamo coi bisestili e va tutto a pallino...
Proviamo con i giorni.
        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,9,1);

        Long milliDiff=scuola.getTime()-nascita.getTime();
        Long giorni=milliDiff/1000/3600/24;
        System.out.println(giorni);
12-03 15:11:16.978 14262-14262/? I/System.out: 1852
Posso dividere per 365 e ottenere gli anni.
        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,9,1);

        Long milliDiff=scuola.getTime()-nascita.getTime();
        Long giorni=milliDiff/1000/3600/24/365;
        System.out.println(giorni);
12-03 15:19:57.393 14669-14669/? I/System.out: 5
Ma esiste la possibilità, invece di ridurre tutto a millisecondi, di fare il calcolo direttamente fra giorni, mesi e anni?
Proviamo...

        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,9,1);


        int anni=scuola.getYear()-nascita.getYear();
        System.out.println(anni);
12-03 15:26:58.240 14902-14902/? I/System.out: 5
Sì, esatto. Tutto deprecato, ma tutto funzionante!
Quindi per calcolare l'età questo è perfetto!

Resta da calcolare gli anni esatti al giorno e al mese.
Ho questo schema...
        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,9,1);


        int differenza=scuola.getYear()-nascita.getYear();
        if(nascita.getMonth()>scuola.getMonth() ||
                (nascita.getMonth()==scuola.getMonth() && nascita.getDate()>scuola.getDate())){
            differenza--;
        }
        System.out.println(differenza);
12-03 15:34:18.835 15111-15111/? I/System.out: 5
Bene. Se invece avessi iniziato la scuola il 1 agosto 1966:
        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,7,1);


        int differenza=scuola.getYear()-nascita.getYear();
        if(nascita.getMonth()>scuola.getMonth() ||
                (nascita.getMonth()==scuola.getMonth() && nascita.getDate()>scuola.getDate())){
            differenza--;
        }
        System.out.println(differenza);
12-03 15:36:36.880 15192-15192/? I/System.out: 4
...avrei iniziato la scuola ancora a 5 anni non compiuti.
Se avessi iniziato il giorno prima del mio compleanno:
        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,8,4);


        int differenza=scuola.getYear()-nascita.getYear();
        if(nascita.getMonth()>scuola.getMonth() ||
                (nascita.getMonth()==scuola.getMonth() && nascita.getDate()>scuola.getDate())){
            differenza--;
        }
        System.out.println(differenza);
12-03 15:38:13.616 15288-15288/? I/System.out: 4
Mentre se fossi andato il giorno del mio compleanno:
        Date nascita=new Date(1961,8,5);
        Date scuola=new Date(1966,8,5);


        int differenza=scuola.getYear()-nascita.getYear();
        if(nascita.getMonth()>scuola.getMonth() ||
                (nascita.getMonth()==scuola.getMonth() && nascita.getDate()>scuola.getDate())){
            differenza--;
        }
        System.out.println(differenza);
12-03 15:39:19.222 15372-15372/? I/System.out: 5
Sarei andato a 5 anni compiuti!
Bene. Non tenendo conto delle deprecazioni, con i metodi di Date si può fare qualunque calcolo!

venerdì 2 dicembre 2016

Codice che interpreta la data in una editText

Ecco il codice definitivo:
public class MainActivity extends AppCompatActivity {

    SimpleDateFormat sdf;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Date date=null;
        String stringa="5 09 961";



        int posizione=stringa.length()-stringa.lastIndexOf(" ");

        if(posizione!=5 && posizione!=3)return;

        if(posizione==5){
            System.out.println("Posizione 5");
            sdf=new SimpleDateFormat("dd M yyyy");
        }
        if(posizione==3){
            System.out.println("Posizione 3");
            sdf=new SimpleDateFormat("dd M yy");
        }

        try {
            sdf.setLenient(false);
            date = sdf.parse(stringa);
            System.out.println(date.toString());
        } catch (ParseException e) {
            System.out.println("ERRORE");
        }


    }
}

giovedì 1 dicembre 2016

Inserimento della data e riconoscimento di vari formati di date

Obiettivo: Permettere il riconoscimento come data di vari formati inseriti liberamente.

Vado subito a fare una ricerca...

Teniamo presente che bisogna comunque e sempre usare la classe Calendar.

E creiamo un'istanza di Calendar...
Calendar calendar= Calendar.getInstance();
E fin qui non ci sono dubbi, anche se non abbiamo risolto niente.

La classe che aveva un ruolo, se ricordo bene, nella conversione di date, era SimpleDateFormat.
Provo ad istanziarla...

.....

Bene, ho fatto un po' di ricerche e pare che l'unico modo sia preparare una serie di formati di data confrontandoli con ciò che viene scritto.
Quali possono essere i formati di data più "immediati"?
Tipo:
  1. 5 9 1961
  2. 5 9 61
  3. 05 09 1961
  4. 05 09 61
  5. 5/9/1961
  6. 5/9/61
  7. 05/09/1961
  8. 05/09/61
... mi sta venendo un'idea: e se attraverso la manipolazione delle stringhe parificassi i vari formati di data convertendo in spazio vuoto sia gli slash sia gli eventuali trattini?7 Sarebbe fattibile.
Vediamo come si può manipolare una stringa.

Ottimo!
Praticamente il metodo replace() fa perfettamente al caso mio.
Per convertire una stringa in cui le varie parti di una data siano separate da uno spazio qualora venga introdotta con degli slash, si può usare questa sintassi:
        String stringa="5/9/1961";

        stringa=stringa.replace('/',' ');

        System.out.println(stringa);
Ed ecco l'output:
12-01 13:53:38.906 4629-4629/? I/System.out: 5 9 1961
Stessa cosa con il trattino:
        String stringa="5-9-1961";

        stringa=stringa.replace('-',' ');

        System.out.println(stringa);
12-01 13:54:50.784 4717-4717/? I/System.out: 5 9 1961
Se vengono presi in considerazione caratteri che non fanno parte della stringa, vengono bellamente ignorati:
        String stringa="5 9 1961";

        stringa=stringa.replace('/',' ');

        System.out.println(stringa);
12-01 13:56:24.096 4808-4808/? I/System.out: 5 9 1961
Questo mi consente di mettere in sequenza le "normalizzazioni" della stringa.
Ora torno al codice che mi deve parsare la stringa.
L'uso di setLenient=false è importante per far lanciare eccezione qualora il formato della stringa non rispecchi quello voluto.

Bene.
Mi costruisco un array di SimpleDateFormat.
Ecco il codice che ho scritto: sembra che sia sufficiente usare come parametro di SimpleDateFormat la stringa "dd MM yyyy" per accettare tutti i vari formati che avevo elencato sopra, tranne quando la separazione tra le cifre è fatta con uno slash o un trattino.
        ArrayList<SimpleDateFormat> lista=new ArrayList<SimpleDateFormat>();
        lista.add(new SimpleDateFormat("dd/MM/yyyy"));

        for(SimpleDateFormat format : lista){
            try {
                format.setLenient(false);
                format.parse(stringa);
                System.out.println("CORRETTO");
            } catch (ParseException e) {
                System.out.println("ERRORE");
            }

        }
Credo di poter lavorare con una certa dose di correttezza sufficiente se pongo che il mese vada sempre introdotto in numero, riportando tutto a un formato "normale" mediante la sostituzione dei simboli di separazione con spazi vuoti.
No! C'è qualche problema con l'anno.
Se io scrivo yyyy e introduco l'anno in due cifre, me lo conta senza le prime due cifre, ossia il 61 dopo Cristo.
Devo poter discriminare dalla stringa se l'ultimo pezzo dopo lo spazio vuoto è di due o quattro cifre.

Come fare?
Mediante il metodo lastIndexOf della stringa posso risalire, sottraendo la posizione del carattere dalla lunghezza della stringa, alla posizione da destra del carattere.
Ecco come ho risolto per il momento il problema. L'utente viene obbligato a scrivere l'anno in due o quattro cifre, se ne usa un numero diverso incorre nell'errore.
La data è giusta comunque:
public class MainActivity extends AppCompatActivity {

    SimpleDateFormat sdf;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Date date=null;
        String stringa="5 09 961";

        int posizione=stringa.length()-stringa.lastIndexOf(" ");

        if(posizione==5){
            sdf=new SimpleDateFormat("dd M yyyy");
        }
        if(posizione==3){
            sdf=new SimpleDateFormat("dd M yy");
        }
        else{
            System.out.println("ERRORE");
        }

        try {
            date=sdf.parse(stringa);
            System.out.println(date.toString());
        } catch (ParseException e) {
            System.out.println("ERRORE");
        }

    }
}

mercoledì 30 novembre 2016

Obiettivo: mettere un'AlertDialog

Obiettivo: mettere una casella di dialogo per la cancellazione di un elemento dal database.
Trovato:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {

                //i valori delle textviews della row servono per la finestra di dialogo.
                final TextView txtNome=(TextView)view.findViewById(R.id.txtNome);
                final TextView txtCognome=(TextView)view.findViewById(R.id.txtCognome);

                //codice per la finestra di dialogo
                new AlertDialog.Builder(context)
                        .setMessage("SEI SICURO DI VOLER CANCELLARE "+txtNome.getText()+" "+txtCognome.getText()+"?")
                        .setPositiveButton("SI", new DialogInterface.OnClickListener(){

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                helper.cancella(txtNome.getText().toString(),txtCognome.getText().toString());
                                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                                crs=myCursorAdapter.swapCursor(c);

                            }
                        })
                        .setNegativeButton("NO",new DialogInterface.OnClickListener(){

                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        })
                        .show();
                //------FINE CODICE per la finestra di dialogo


                return true;


            }
        });
Funziona.
Per il NO ho lasciato vuoto il comando, dal momento che non intendo fare nulla.
Notevole (marcato in rosso) il fatto che ottengo un messaggio di errore che chiama in causa il Theme usato, se non imposto il context coerentemente con quello dell'Activity.
Per far questo ho aggiunto all'inizio:
public class MainActivity extends AppCompatActivity {
    Context context;
    Cursor crs;
    EditText editCognome;
    EditText editNome;
    MyCursorAdapter myCursorAdapter;
    ListView listView;
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        context=this;
impostando poi a context il contesto della AlertDialog.Builder.
E così funziona.

Cancellare un record in risposta a un long click sulla ListView

Obiettivo: Cancellare un record in risposta a un long click sulla ListView.
Costruire nell'ambito dell'Helper la funzione che cancella un record dalla tabella del database.

    //funzione che cancella un record dalla tabella tblnomi del database
    public void cancella(String s){
        SQLiteDatabase db=this.getWritableDatabase();
        db.delete("tblnomi","nome=?",new String[]{s});

    }
Adesso la uso:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                helper.cancella("Giuseppe");
                return true;
            }
        });
Al long click di qualunque elemento della ListView dovrei avere la cancellazione del record il cui campo "nome" equivale a "Giuseppe".
Proviamolo...

Sì, me lo ha cancellato.
Ho dimenticato di ricaricare l'adapter, però:
Procedo (con un altro nome perché di Giuseppe ce n'era uno solo).
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                helper.cancella("Giovanni");
                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                crs=myCursorAdapter.swapCursor(c);
                return true;
            }
        });
Sì, funziona.

Ora devo però rendere la cosa specifica: devo risalire al nome riportato nell'elemento della listView.
Ecco, dopo aver trovato e sperimentato il modo di accedere alle textView in cui sono riportati nome e cognome, modifico la funzione di helper in modo da includere anche il cognome:
    //funzione che cancella un record dalla tabella tblnomi del database
    public void cancella(String n, String c){
        SQLiteDatabase db=this.getWritableDatabase();
        db.delete("tblnomi","nome=? and cognome=?",new String[]{n,c});
...e riscrivo la funzione in Main:
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                TextView txtNome=(TextView)view.findViewById(R.id.txtNome);
                TextView txtCognome=(TextView)view.findViewById(R.id.txtCognome);
                helper.cancella(txtNome.getText().toString(),txtCognome.getText().toString());
                Cursor c=helper.queryTotale(editNome.getText().toString(),editCognome.getText().toString());
                crs=myCursorAdapter.swapCursor(c);
                return true;
            }
        });
Vediamo se funziona...

Funziona egregiamente!!! Obiettivo raggiunto!

sabato 26 novembre 2016

Database SQLite: ottenere record unici per un campo o per una combinazione di campi.

Ho re-imparato come rendere unici i records di un database, rendendo unico il valore di un campo e rendendo unica una combinazione di valori di campi.

Rendere unico il valore di un singolo campo:
public class Helper extends SQLiteOpenHelper {
    public Helper(Context context) {
        super(context, "database.db",null,1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE NOMI(_ID INTEGER PRIMARY KEY, " +
                "NOME TEXT NOT NULL UNIQUE, COGNOME TEXT)");
    }

    public void save(String nome, String cognome){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME", nome);
        values.put("COGNOME", cognome);
        try {
            db.insertOrThrow("NOMI", null, values);
        }catch(Exception e){
            System.out.println("ERRORE");
        }
    }
Per avere una gestione dell'errore devo usare insertOrThrow in luogo di insert.

Ora provo a inserire due records con il nome uguale.
        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Bianchi");
    }
}
11-26 17:55:24.511 31319-31319/? I/System.out: ERRORE

e il database contiene solo il primo record.
Rendere unico il valore di una combinazione di campi:
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE NOMI(_ID INTEGER PRIMARY KEY, " +
                "NOME TEXT, COGNOME TEXT, UNIQUE(NOME, COGNOME))");
    }

    public void save(String nome, String cognome){
        SQLiteDatabase db=this.getWritableDatabase();
        ContentValues values=new ContentValues();
        values.put("NOME", nome);
        values.put("COGNOME", cognome);
        try {
            db.insertOrThrow("NOMI", null, values);
        }catch(Exception e){
            System.out.println("ERRORE");
        }
    }
Ora riprovo a inserire i due record di prima con solo il nome uguale:
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Bianchi");
    }
}
Nessuna segnalazione di errore:


...e la tabella del database contiene tutti e due i records.

Ora provo a inserire due record uguali per nome e per cognome:
    Helper helper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        helper=new Helper(this);
        helper.save("Mario","Rossi");
        helper.save("Mario","Rossi");
    }
}
11-26 18:02:17.103 3062-3062/? I/System.out: ERRORE

...e il database contiene solo il primo record.

Ora vorrei un'altra funzione: cercare un record sul database, se presente restituire l'id, se assente inserire il nuovo dato.
Si potrebbe gestire con le funzioni di cui sopra...

giovedì 17 novembre 2016

Misure dei tasti J-Keyboard

Ho stabilito le misure per un tasto, almeno per quello DO MI FA SI:


Ora vado avanti con il posizionamento di altri tasti.

440 Hz audioTrack

Ecco il codice per l'emissione di un LA (440 Hz):
public class MainActivity extends AppCompatActivity {

    AudioTrack audioTrack;
    Button bottone;
    int minBufferSize=AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_8BIT);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        bottone=(Button)findViewById(R.id.button);

        playSound(440,10);


    }

    private void playSound(double frequency, int duration) {
        // AudioTrack definition
        int mBufferSize = AudioTrack.getMinBufferSize(44100,
                AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_8BIT);

        AudioTrack mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT,
                mBufferSize, AudioTrack.MODE_STREAM);

        // Sine wave
        double[] mSound = new double[1000000];
        short[] mBuffer = new short[1000000];
        for (int i = 0; i < mSound.length; i++) {
            mSound[i] = Math.sin((2.0*Math.PI * i/(44100/frequency)));
            mBuffer[i] = (short) (mSound[i]*Short.MAX_VALUE);
        }

        mAudioTrack.setStereoVolume(AudioTrack.getMaxVolume(), AudioTrack.getMaxVolume());
        mAudioTrack.play();

        mAudioTrack.write(mBuffer, 0, mSound.length);
        mAudioTrack.stop();
        mAudioTrack.release();

    }

} 
Bene. Me lo salvo e poi vedrò come utilizzarlo.

martedì 15 novembre 2016

Audiotrack: per far emettere toni puri al cellulare.

Ora proviamo a costruire un dispositivo che emetta una frequenza sonora.
Dobbiamo costruire la classe AndroidAudioDevice.
Ci provo.

Dichiariamo intanto le due variabili, l'una che mi sembra la più importante, di tipo AudioTrack, e l'altra che è una matrice di short, il buffer.
    class AndroidAudioDevice{
        AudioTrack track;
        short[] buffer = new short[1024];
    }
Bene. E ora?
Ora c'è il costruttore, in cui: si ottiene un int minSize, che dovrebbe essere la minima dimensione del buffer.
Vediamolo
    class AndroidAudioDevice{
        AudioTrack track;
        short[] buffer = new short[1024];

        int minSize=AudioTrack.getMinBufferSize(44100,
                AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_16BIT);
    }
Quindi si definisce track:
        public AndroidAudioDevice() {
            int minSize = AudioTrack.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);

            track = new AudioTrack(AudioManager.STREAM_MUSIC,
                    44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minSize,
                    AudioTrack.MODE_STREAM);
           track.play();
        }
Aggiungo track.play() ed ecco tutto il costruttore.
Ora bisognerebbe creare il metodo writeSamples che ha per parametri un array di float.
Provo...
        public void writeSamples(float[] samples){
            fillBuffer(samples);
        }
Questo passa subito al metodo privato fillBuffer (che ancora non esiste) il suo parametro array di float.
Quindi scrive su track quello che legge sul buffer che è "di ritorno" da fillBuffer.
        public void writeSamples(float[] samples){
            fillBuffer(samples);
            track.write(buffer,0,samples.length);
        }
E ora resta da definire fillBuffer.

        private void fillBuffer(float[] samples){
            
        }
nel quale prima si adeguano le dimensioni del buffer all'array di samples.
        private void fillBuffer(float[] samples){
            if(buffer.length < samples.length){
                buffer=new short[samples.length];
            }
        }
e quindi si prendono uno per uno tutti i float di samples e si convertono in short per trasferirli nell'array di short che è buffer.
                for(int i=0;i<samples.length;i++){
                    buffer[i]=(short)(samples[i]*Short.MAX_VALUE);
                }
Ecco.
Mi riscrivo qui il codice completo:
public class MainActivity extends AppCompatActivity {


    Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    class AndroidAudioDevice{
        AudioTrack track;
        short[] buffer = new short[1024];

        public AndroidAudioDevice() {
            int minSize = AudioTrack.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);

            track = new AudioTrack(AudioManager.STREAM_MUSIC,
                    44100,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minSize,
                    AudioTrack.MODE_STREAM);
            track.play();
        }

        public void writeSamples(float[] samples){
            fillBuffer(samples);
            track.write(buffer,0,samples.length);
        }

        private void fillBuffer(float[] samples){
            if(buffer.length < samples.length){
                buffer=new short[samples.length];

                for(int i=0;i<samples.length;i++){
                    buffer[i]=(short)(samples[i]*Short.MAX_VALUE);
                }
            }
        }
    }
} 

Copiare un file da una parte all'altra del dispositivo.

Ho avuto non poche difficoltà nel copiare un file da una parte all'altra della memoria del cellulare.
Alla fine ci sono riuscito.
Credo di padroneggiare abbastanza le tecniche per la creazione di un file, ma ho avuto difficoltà nella copia mediante FileInputStream e FileOutputStream

Ora riprovo a trasferirmi un file dalla cartella DCIM/Camera alla cartella DCIM/Screenshots.
Il file è casa.jpg.

Ottengo l'accesso all'external storage.
File sd= Environment.getExternalStorageDirectory();
Voglio sapere qual è il Path di questo file. Come faccio?
Provo:
        File sd= Environment.getExternalStorageDirectory();
        System.out.println("Path: "+Environment.getExternalStorageDirectory().getAbsolutePath());
11-15 17:44:47.621 13976-13976/? I/System.out: Path: /storage/emulated/0
Ora voglio i nomi di tutti i sottofiles e le sottocartelle di questo External Storage.
        File sd= Environment.getExternalStorageDirectory();
        System.out.println("Path: "+Environment.getExternalStorageDirectory().getAbsolutePath());
        
        for(File f : sd.listFiles()){
            System.out.println(f.toString());
            
        }
Proviamo:
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Samsung
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Android
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/DCIM
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Music
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Podcasts
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Ringtones
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Alarms
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Notifications
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Pictures
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Movies
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Download
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Playlists
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/WhatsApp
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.aide
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/AppProjects
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/docs
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Voice Recorder
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/com.facebook.katana
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/runtastic
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/com.facebook.orca
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.facebook_cache
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/MBSTPH
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/MBSTGO
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/VIRAL
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/LazyList
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/Tencent
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/pacer_steps_chart.jpg
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/JavaScript
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.mrnom
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/adb
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/CallRecordings
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.SMT
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.Studio
11-15 17:48:49.941 15616-15616/? I/System.out: /storage/emulated/0/.SPenSDK30
Okay...

Ora voglio scendere in DCIM/Camera/casa.jpg.
DCIM è una sottocartella dell'External Storage.
Come faccio a ottenere un file?
        File sd= Environment.getExternalStorageDirectory();

        File src=new File(sd,"//DCIM//Camera//casa.jpg");

        File dst=new File(sd,"//DCIM//Screenshots//casa.jpg");
Ecco i files corrispondenti all'immagine originale e alla copia che voglio creare.

Ora devo usare gli Stream...
E qua c'è tutto l'apparato.
        File sd= Environment.getExternalStorageDirectory();

        File src=new File(sd,"//DCIM//Camera//casa.jpg");

        File dst=new File(sd,"//DCIM//Screenshots//casa.jpg");

        try {
            FileInputStream in=new FileInputStream(src);
            FileOutputStream out=new FileOutputStream(dst);
            byte[] buffer=new byte[1024];
            int len;
            while((len=in.read(buffer))!=-1) {
                out.write(buffer,0,len);
            }
            in.close();
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
Intanto vediamo se funziona...

Sì, è riuscito! In Screenshots mi trovo il file casa.jpg che funziona.


Ora vediamo come "funziona".
Il metodo read(buffer) di FileInputStream legge dei bytes dal file in un buffer di bytes..
Restituisce un int che rappresenta il numero di bytes letti, mentre se non ci sono più bytes da leggere dal file al buffer, restituisce -1.
Ecco perché
            while((len=in.read(buffer))!=-1) {
                out.write(buffer,0,len);
            }
...finché len, che è uguale al numero di bytes letto nel buffer, è diverso da -1, va scritto nel FileOutputStream quel numero di bytes preso dal buffer.
E forse, finalmente lo abbiamo capito.

lunedì 14 novembre 2016

AudioTrack, per far emettere toni allo smartphone. Scontro con i numeri in virgola mobile.

Questo è il link per la riproduzione di toni.

Bisogna costruire una classe AndroidAudioDevice.
Poi si definiscono delle variabili che sono una di tipo AudioTrack e una di tipo matrice di short (short[]).
Quindi il costruttore.
Nel costruttore si definisce minSize (che dovrebbe essere la dimensione minima del buffer).
minsize viene definito usando il metodo AudioTrack.getBufferSize.
Questo deve restituire un int.
Usa come parametri 44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT.

44100 è la frequenza di campionamento.
Il secondo parametro è MONO o STEREO.
Il terzo è il formato dei dati, 8 bit o 16 bit o altro (che al momento non mi viene in mente).

Segue il costruttore dell'oggetto di classe AudioTrack specificato fra le variabili iniziali, che usa come parametri il già visto minSize più i parametri che il metodo getMinSize utilizzava, più altri due parametri.
Quindi nel costruttore si dà un valore alla variabile track di classe AudioTrack, e quindi si invoca il metodo play() di questa AudioTrack.

La classe che sto creando, ossia AndroidAudioDevice, oltre al costruttore ha anche il metodo writeSamples().
   public void writeSamples(float[] samples) 
   { 
      fillBuffer( samples );
      track.write( buffer, 0, samples.length );
   }
Questo metodo ha come parametro una matrice di tipo float[] e la usa come parametro del metodo privato fillBuffer().
Vediamolo:
   private void fillBuffer( float[] samples )
   {
      if( buffer.length < samples.length )
         buffer = new short[samples.length];
 
      for( int i = 0; i < samples.length; i++ )
         buffer[i] = (short)(samples[i] * Short.MAX_VALUE);
   } 
Accoglie come parametro la matrice float[] che gli viene passata da writeSamples che lo invoca.
Con le prime due righe...
      if( buffer.length < samples.length )
         buffer = new short[samples.length];
...se la grandezza del buffer è di dimensioni inferiori a quella del parametro che viene passato, crea una nuova matrice come buffer, di dimensioni uguali a quelle del parametro;
quindi per ogni elemento contenuto nella matrice samples si prende un elemento del buffer e gli si dà il valore, castato a short, del corrispondente elemento di samples moltiplicato per Short.MAX_VALUE.
Cosa significa questo Short.MAX_VALUE?


Viene moltiplicato un tipo float per Short.MAX_VALUE.
Vediamo se riesco a trovare qualche ispirazione cercando intorno alla conversione short -> float.

Per capire tutto questo forse devo ripassarmi bene come sono strutturati i numeri in virgola mobile.

Quando io moltiplico un float per uno short, lo short viene preventivamente convertito in float?
Se do a uno short un valore superiore al suo massimo, che valore assume la variabile?

Vediamo...

Mi devo costruire dei modelli.

Ecco, come ricordavo, la moltiplicazione di un float per uno short dà un risultato di tipo float.
        float numeroFloat = 5.2f;

        short numeroShort=2;

        float risultato=numeroFloat * numeroShort;

        System.out.println(risultato);
Ottengo come risultato:
11-14 17:06:10.752 4130-4130/? I/System.out: 10.4

Se invece do al risultato il tipo short:
        float numeroFloat = 5.2f;

        short numeroShort=2;

        short risultato=numeroFloat * numeroShort;

        System.out.println(risultato);
...ottengo un errore, perché il risultato non può essere short ma è float.
Ora è il caso di ripassare a dovere i numeri in virgola mobile, perché credo che una risposta mi può venire solo da lì.

domenica 13 novembre 2016

Conflitti nell'attivazione del timer.

Vado cercando il modo di schematizzare quando sia necessario avere il timer in funzione e quando no.
Cerchiamo di iniziare dalle cose più semplici.

Innanzitutto, quando si avvia il programma il timer è già in funzione.
Questo innesca, dopo una quantità di minuti prestabilita, la comparsa della Activity B.
L'Activity B, una volta chiusa, innnesca di nuovo il timer per la ricomparsa di se stessa, e così via, fin quando non si interrompa il timer mediante un pulsante presente sull'Activity A.
Il primo timer è innescato da A, il secondo sempre da B che si richiama da solo alla scadenza del tempo prestabilito.

E' possibile che, se si schiaccia due volte il pulsante su A che innesca il timer, si abbiano due timer, in modo che B appaia due volte, ossia che ne vengano aperte più istanze?
Questo sarebbe problematico, e devo verificarlo.
Nel mio caso, il timer si avvia all'accensione dello switch.
Questo timer dovrebbe essere l'unico avviato dall'activity A.
Quindi dovrebbe proseguire l'innesco automatico del timer da parte dell'activity B che richiama se stessa.
Ci sono però altre tre occasioni in cui viene innescato il timer esternamente al normale ciclo dell'activity B che richiama se stessa:
  1. alla conferma di una nuova voce;
  2. alla pressione del tasto VISUALIZZA (indirettamente mediante apertura di activity B con successivo innesco del timer);
  3. dall'activity SETTINGS nell'evento onPause (se questo prelude al ritorno all'Activity A).
Me li rinfresco da codice:
        bttConferma=(Button)findViewById(R.id.bttConferma);
        bttConferma.setEnabled(false);
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bottone.setEnabled(true);
                avviaTimer();
                mOnAlarmChangeListener.OnAlarmChange();
            }
        });
        bttVisualizza=(Button)findViewById(R.id.bttVisualizza);
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });


Activity SETTINGS:
    @Override
    protected void onPause(){
        super.onPause();

        if(permessoAvvioTimer)avviaTimer();
    }
Se il timer viene innescato da una di queste e successivamente dall'altra, si hanno due cicli di Timer sovrapposti.
Provo a causarlo coscientemente togliendo cancellaTimer() quando viene visualizzata l'altra activity dal pulsante su A.
        bttVisualizza=(Button)findViewById(R.id.bttVisualizza);
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                //cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });
E proviamo ad accendere l'interruttore e prima che si apra B a visualizzarla col pulsante.

Sì, ottengo un conflitto e vengono aperte due istanze di B!
Forse l'unica è inserire un cancellaTimer() ogni volta prima che il timer venga innescato esternamente.
Lo faccio nei punti prima considerati:
1) Interruttore:
        switch1.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked){
                    cancellaTimer();
                    avviaTimer();
                    mOnAlarmChangeListener.OnAlarmChange();
                }else{
                    cancellaTimer();
                    mOnAlarmChangeListener.OnAlarmChange();
                }
            }
        });


2) Conferma di una nuova voce:
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bottone.setEnabled(true);
                txtVocale.setText(null);
                cancellaTimer();
                avviaTimer();
                mOnAlarmChangeListener.OnAlarmChange();
            }
        });


3) Pressione del tasto Visualizza:
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });
4) Da SETTINGS in onPause:
    @Override
    protected void onPause(){
        super.onPause();

        if(permessoAvvioTimer){
            cancellaTimer();
            avviaTimer();
        }
    }


Dovrei essere piuttosto sicuro, adesso...

Semplifico il codice dell'interruttore:
        switch1.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                cancellaTimer();
                if(isChecked) avviaTimer();
                
                mOnAlarmChangeListener.OnAlarmChange();
                
                
            }
        });
E ora sperimentiamo nel tempo...

venerdì 11 novembre 2016

Come terminare una Activity e rimuoverla dai Recent Tasks.

A questo punto, le cose da fare sono diverse.
Per prima cosa, terminare la schermata iniziale.
Ho aggiunto un'altra voce nel menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/stopTimer"
        android:title="Stop Timer">

    </item>
    <item
        android:id="@+id/showSettings"
        android:title="Impostazioni" >
    </item>
    <item
        android:id="@+id/terminaSchermata"
        android:title="Nascondi" >
    </item>
</menu> 
E la gestisco con questo codice:
    @Override
    public boolean onOptionsItemSelected(MenuItem menuItem){
        switch(menuItem.getItemId()){
            case R.id.stopTimer:
                cancellaTimer();
                break;
            case R.id.showSettings:
                cancellaTimer();;
                Intent i=new Intent(MainActivity.this,Settings.class);
                startActivity(i);
                break;
            case R.id.terminaSchermata:
                    finish();
            }
                
        }
        return true;
    }
Però mi rimane nei recent tasks.
E' già impostato nel manifest autoRemoveFromRecents:
        <activity
            android:name=".Lista"
            android:autoRemoveFromRecents="true"
            android:screenOrientation="portrait" /> 
Ma, come ho imparato, nelle versioni superiori a 21 è necessario usare, per terminare e rimuovere dai recenti, finishAndRemoveTasks.
Lo uso e ottengo errore perché l'applicazione è indirizzata anche a versioni più antiche della 21.
Per far ciò, trovo il codice che adatta l'istruzione alla versione:
            case R.id.terminaSchermata:
                if(Build.VERSION.SDK_INT>=21){
                    finishAndRemoveTask();
                }else{
                    finish();
                }
E così funziona, rimuovendo completamente l'activity dai recenti una volta terminata.

Ripasso BroadcastReceiver nel tentativo di intercettare il tasto Home.

Provo a creare un broadcastReceiver.
Lo creo nello stesso modulo.

L'ho nidificato nella classe dell'Activity:
    public class MyReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            
        }
    }
Il metodo onReceive viene dato di default.

Per la registrazione, bisogna prima istanziare la classe creata.
Ci provo...

Scrivo intanto un'azione per l'evento onReceive:
    public class MyReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            System.out.println("ricevuto");
        }
    }


Ecco:
public class MainActivity extends AppCompatActivity {

    MyReceiver myReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myReceiver=new MyReceiver();

    }
Ora devo definire un IntentFilter e registrare il Receiver insieme a questo IntentFilter in onResume, deregistrandolo in onPause
Provo a usare l'intentFilter che ho trovato nella costruzione dell'HomeWatcher:
        myReceiver=new MyReceiver();
        intentFilter=new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
Quindi registro in onResume e deregistro in onPause:
    @Override
    protected void onResume(){
        super.onResume();
        registerReceiver(myReceiver,intentFilter);
    }

    @Override
    protected void onPause(){
        super.onPause();
        unregisterReceiver(myReceiver);
    }
Funziona. Intercetta la pressione del tasto Home.
Ma non mi previene la chiusura dell'activity.

martedì 8 novembre 2016

Sequenza dei pulsanti nell'applicazione J-Ag.

Ottimizziamo il ciclo...

Quando appare la schermata principale, i tasti Conferma e Annulla sono disattivati.
Sistemo il pulsante Annulla.

Ecco: a inizio di schermata, NUOVO è attivo, mentre CONFERMA e ANNULLA sono inattivi.

Poi, una volta recepito il comando verbale, si attivano CONFERMA e ANNULLA per le due scelte.


E' necessario che non vi sia l'esibizione dell'agendina al termine di questa schermata, ma semplicemente un avvio e uno stop del timer.
Correggo.

r Fatto.
E ho trovato una successione per i tre pulsanti, NUOVO, CONFERMA e ANNULLA.
Quando appare la schermata, NUOVO è abilitato, mentre CONFERMA e ANNULLA sono disabilitati, in modo che si possa premere solo NUOVO.
        bttAnnulla=(Button) findViewById(R.id.bttAnnulla);
        bttAnnulla.setEnabled(false);
.....

        bttConferma=(Button)findViewById(R.id.bttConferma);
        bttConferma.setEnabled(false);
Quando schiaccio NUOVO, dopo la ricezione della voce, si va a onActivityResult e, SE IL RISULTATO E' RESULT_OK, ossia -1, si abilitano CONFERMA e ANNULLA, mentre si disabilita NUOVO:
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode==RESULT_OK){
            lista=data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            tts.speak(lista.get(0),TextToSpeech.QUEUE_FLUSH,null);

            
            bottone.setEnabled(false);
            bttConferma.setEnabled(true);
            bttAnnulla.setEnabled(true);

        }else{r
            
            avviaTimer();
        }



    }
Se si sceglie di schiacciare CONFERMA, viene salvato il dato, quindi si disabilitano CONFERMA e ANNULLA mentre si riabilita NUOVO, e riparte il TIMER
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bttAnnulla.setEnabled(false);
                bottone.setEnabled(true);
                avviaTimer();
            }
        });
Se invece si sceglie di schiacciare ANNULLA, non viene salvato nessun dato, e ugualmente si disabilitano CONFERMA e ANNULLA, mentre si riabilita NUOVO, e riparte il TIMER.
        bttAnnulla.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                bttConferma.setEnabled(false);
                bttAnnulla.setEnabled(false);
                bottone.setEnabled(true);
                avviaTimer();
            }
        });


Se invece si evita di dare un input vocale e si schiaccia INDIETRO o si schiaccia al di fuori del prompt vocale, si agisce più a monte, e si dà un risultato sbagliato ad onActivityResult, per cui RESULT_OK è pari a zero invece che a -1: di conseguenza entra in gioco questo codice, che semplicemente riavvia il TIMER: dal momento che RESULT_OK è pari a zero, non si attivano CONFERMA e ANNULLA.
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode==RESULT_OK){
            lista=data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            tts.speak(lista.get(0),TextToSpeech.QUEUE_FLUSH,null);


            bottone.setEnabled(false);
            bttConferma.setEnabled(true);
            bttAnnulla.setEnabled(true);

        }else{

            avviaTimer();
        }

    }

domenica 6 novembre 2016

Aggiunta di una suoneria e codice per l'impostazione di una suoneria personalizzata.

Riscriviamo un codice che suoni la suoneria di default.
        Uri ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_NOTIFICATION);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Questo suona la suoneria di tipo NOTIFICATION.

Esistono anche altri tipi:
        Uri ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_ALARM);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Questo invee suona la suoneria di tipo ALARM (che non la smette più!)

Andiamo a vedere l'altro tipo:

       Uri ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_RINGTONE);
        Ringtone ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Questo suona la suoneria telefonica.


C'è poi il capitolo della scelta della suoneria personalizzata per l'applicazione, che non ricordo.
Rinfreschiamo la memoria...

Ci si avvale di SharedPreferences.
Se non è stato salvato niente, ossia se la stringa ripescata da SharedPreferences è vuota, si usa la suoneria di default, altrimenti si Uri.parsa la stringa reperita in SharedPreferences.
Devo definire, però SharedPreferences.
    SharedPreferences SP;

.....

        SP=getApplicationContext().getSharedPreferences("suoneria",Context.MODE_PRIVATE);
Fatto questo, torno a modificare il codice che stabilisce la suoneria:
        SP=getApplicationContext().getSharedPreferences("suoneria",Context.MODE_PRIVATE);

        Uri ringtoneUri;
        Ringtone ringtone=null;
        String strSuoneria=SP.getString("suoneria","");
        if(TextUtils.isEmpty(strSuoneria)){
            ringtoneUri= RingtoneManager.getActualDefaultRingtoneUri(getApplicationContext(),RingtoneManager.TYPE_NOTIFICATION);
            
            
        }else{
            ringtoneUri=Uri.parse(strSuoneria);
        }
        ringtone=RingtoneManager.getRingtone(getApplicationContext(),ringtoneUri);
        ringtone.play();
Il discorso è semplice.

E funziona.
Ora devo provvedere una sezione per la scelta della suoneria.
Creo un'activity Settings per la regolazione di questa e altre funzioni, raggiungibile tramite menu.

Ecco l'XML del menu di MainActivity modificato:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/stopTimer"
        android:title="Stop Timer">

    </item>
    <item
        android:id="@+id/showSettings"
        android:title="Impostazioni" >
    </item>
</menu> 
Ed ecco il menu in MainActivity modificato:
    @Override
    public boolean onCreateOptionsMenu(Menu menu){
        super.onCreateOptionsMenu(menu);
        MenuInflater inflater=this.getMenuInflater();
        inflater.inflate(R.menu.miomenu,menu);
        return true;
    }



    @Override
    public boolean onOptionsItemSelected(MenuItem menuItem){
        switch(menuItem.getItemId()){
            case R.id.stopTimer:
                cancellaTimer();
                break;
            case R.id.showSettings:
                Intent i=new Intent(MainActivity.this,Settings.class);
                startActivity(i);
        }
        return true;
    }
E in Settings possiamo scrivere il codice per la scelta delle suonerie.
Prima l'intent:
        bttSceltaSuoneria.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                Intent intent=new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
                intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,RingtoneManager.TYPE_NOTIFICATION);
                startActivityForResult(intent,0);

            }
        });
(subordinato a un Button)

Quindi onActivityResult:
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        Uri uri=data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
        SharedPreferences.Editor editor=SP.edit();
        editor.putString("suoneria",uri.toString());
        editor.commit();
    }
E vediamo se combina... Comunque ho fatto una faticaccia a scrivere questo codice, completamente dimenticato.