Android应用实践-CriminalTucao

CriminalTucao这个小应用可以详细记录身边的各种陋习,这个小应用记录下来的陋习记录包含标题、具体时间以及照片,可以通过邮件、短信、QQ、微信等应用发送给想要吐槽的对象。

一、应用界面

1.空白页

在没用吐槽记录的时候,增加空白页能够提供更佳的用户视觉体验。

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

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:gravity="center" >

        <ListView
            android:id="@android:id/list"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >
        </ListView>

        <TextView
            android:id="@android:id/empty"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_gravity="center_horizontal"
            android:gravity="center_horizontal"
            android:text="@string/empty_view"
            android:textSize="24sp" />
    </LinearLayout>

</FrameLayout>

2.FragmentCrime布局

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

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:orientation="horizontal" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="4dp"
            android:orientation="vertical" >

            <ImageView
                android:id="@+id/crime_imageView"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:background="@android:color/darker_gray"
                android:cropToPadding="true"
                android:scaleType="centerInside" />

            <ImageButton
                android:id="@+id/crime_imageButton"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:src="@android:drawable/ic_menu_camera" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >

            <TextView
                style="?android:listSeparatorTextViewStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/crime_title_label" />

            <EditText
                android:id="@+id/crime_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="16dp"
                android:layout_marginRight="16dp"
                android:hint="@string/crime_title_hint" />
        </LinearLayout>
    </LinearLayout>

    <TextView
        style="?android:listSeparatorTextViewStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/crime_detail_label" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp" >

        <Button
            android:id="@+id/btn_crime_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <CheckBox
            android:id="@+id/crime_solved"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/crime_solved_label" />
    </LinearLayout>

    <Button
        android:id="@+id/crime_suspectButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:text="@string/crime_suspect_text" />

    <Button
        android:id="@+id/crime_reportButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:text="@string/crime_report_text" />

</LinearLayout>

3.吐槽记录布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:background="@drawable/background_activated">
    <!-- 定制列表项 选择框对齐相对布局的右手边 布置两个textview相对于checkbox左对齐 -->
    <CheckBox
        android:id="@+id/crime_list_item_solvedCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_alignParentRight="true"        
        android:enabled="false"
        android:focusable="false"
        android:padding="16dp"
        ></CheckBox>
    <TextView 
        android:id="@+id/crime_list_item_titleTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@id/crime_list_item_solvedCheckBox"
        android:textStyle="bold"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:text="Crime Title"
        />
    <TextView 
        android:id="@+id/crime_list_item_dateTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/crime_list_item_titleTextView"
        android:layout_toLeftOf="@id/crime_list_item_solvedCheckBox"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:paddingTop="4dp"
        android:text="Crime Date"
        />


</RelativeLayout>

4.拍照界面

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

    <LinearLayout 
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <SurfaceView 
            android:id="@+id/crime_camera_surfaceView"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            />
        <ImageButton 
            android:id="@+id/crime_camera_takePictureButton"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:src="@android:drawable/ic_menu_camera"/>
            <!--android:text="@string/take"-->
    </LinearLayout>
    <FrameLayout 
        android:id="@+id/crime_camera_progressContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true">
        <ProgressBar 
            style="@android:style/Widget.ProgressBar.Large"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            />
    </FrameLayout>

</FrameLayout>

5.横屏布局

屏幕切换后能够自适应调整布局。

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

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="4dp"
        android:orientation="horizontal" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="4dp"
            android:orientation="vertical" >

            <ImageView
                android:id="@+id/crime_imageView"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:background="@android:color/darker_gray"
                android:contentDescription="TODO"
                android:cropToPadding="true"
                android:scaleType="centerInside" />

            <ImageButton
                android:id="@+id/crime_imageButton"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:src="@android:drawable/ic_menu_camera" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="vertical" >

            <TextView
                style="?android:listSeparatorTextViewStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/crime_title_label" />

            <EditText
                android:id="@+id/crime_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="16dp"
                android:layout_marginRight="16dp"
                android:hint="@string/crime_title_hint" />
        </LinearLayout>
    </LinearLayout>

    <TextView
        style="?android:listSeparatorTextViewStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/crime_detail_label" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_crime_date"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="4" />

        <CheckBox
            android:id="@+id/crime_solved"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_weight="1"
            android:text="@string/crime_solved_label" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/crime_suspectButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/crime_suspect_text" />

        <Button
            android:id="@+id/crime_reportButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/crime_report_text" />
    </LinearLayout>

</LinearLayout>

二、Crime.java

DAO模型层
右键单击构造方法下方的空白处,选择Source-Generate Getter and Setters选项,为m开头的成员变量生成getter和setter方法。

package com.dw.criminalintent;

import java.util.Date;
import java.util.UUID;
import org.json.JSONException;
import org.json.JSONObject;

//模型层
public class Crime {
    private static final String JSON_ID = "id";
    private static final String JSON_TITLE = "title";
    private static final String JSON_SOLVED = "solved";
    private static final String JSON_DATE = "date";
    private static final String JSON_PHOTO = "photo";
    private static final String JSON_SUSPECT = "suspect";

    private UUID mId;
    private String mTitle;
    private Date mDate;
    private boolean mSolved;
    private Photo mPhoto;
    private String mSuspect;

    public Crime() {
        mId = UUID.randomUUID();// 生成唯一标示
        mDate = new Date();
    }

    public Crime(JSONObject json) throws JSONException {
        mId = UUID.fromString(json.getString(JSON_ID));
        if (json.has(JSON_TITLE)) {
            mTitle = json.getString(JSON_TITLE);
        }
        mSolved = json.getBoolean(JSON_SOLVED);
        mDate = new Date(json.getLong(JSON_DATE));
        if (json.has(JSON_PHOTO)) {
            mPhoto = new Photo(json.getJSONObject(JSON_PHOTO));
        }
        if(json.has(JSON_SUSPECT)){
            mSuspect=json.getString(JSON_SUSPECT);
        }
    }
//实现toJson方法,以json格式保存crime对象
    public JSONObject toJSON() throws JSONException {
        JSONObject json = new JSONObject();
        json.put(JSON_ID, mId.toString());
        json.put(JSON_TITLE, mTitle);
        json.put(JSON_SOLVED, mSolved);
        json.put(JSON_DATE, mDate.getTime());
        if (mPhoto != null)
            json.put(JSON_PHOTO, mPhoto.toJSON());
        json.put(JSON_SUSPECT, mSuspect);
        return json;
    }

    public Date getDate() {
        return mDate;
    }

    public void setDate(Date date) {
        mDate = date;
    }

    public boolean isSolved() {
        return mSolved;
    }

    public void setSolved(boolean solved) {
        mSolved = solved;
    }

    public UUID getId() {
        return mId;
    }

    public void setId(UUID id) {
        mId = id;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }
    public Photo getPhoto() {
        return mPhoto;
    }

    public void setPhoto(Photo photo) {
        mPhoto = photo;
    }

    public String getSuspect() {
        return mSuspect;
    }

    public void setSuspect(String suspect) {
        mSuspect = suspect;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return mTitle;
    }

}

三.CrimeLab.java

创建单例:一个私有构造方法和get方法,若实例已经存在,get方法直接返回它,若不存在,get方法会调用构造方法来创建它,在该类中进行数据持久保存。

package com.dw.criminalintent;

import java.util.ArrayList;
import java.util.UUID;

import android.content.Context;
import android.util.Log;
/**
 * 
 * @author dw
 *
 */
public class CrimeLab {
    private static final String TAG="CrimeLab";
    private static final String FILENAME="crimes.json";
    private static CrimeLab sCrimeLab;//带s前缀表示静态变量
    private Context mAppContext;
    private ArrayList<Crime> mCrimes;
    private CriminalIntentJSONSerializer mSerializer;

//使用context参数,单例可以完成activity的启动、获取项目资源、查找应用的私用存储空间等作用
    public CrimeLab(Context appContext) {
        mAppContext=appContext;
        //mCrimes=new ArrayList<Crime>();
        try {
            mCrimes=mSerializer.loadCrimes();
        } catch (Exception e) {
            //加载失败则新建一个数组列表
            mCrimes=new ArrayList<Crime>();
            Log.e(TAG, "error loading crimes:",e);
        }
        mSerializer=new CriminalIntentJSONSerializer(mAppContext, FILENAME);
        /*for (int i = 0; i < 50; i++) {
            Crime c=new Crime();
            c.setTitle("Crime #"+i);
            c.setSolved(i%2==0);
            mCrimes.add(c);
        }*/
    }
    public static CrimeLab get(Context c){
        if(sCrimeLab==null){
            //调用getApplicationContext方法将不确定是否存在的context替换成application context,它是针对应用的全局性context
            sCrimeLab=new CrimeLab(c.getApplicationContext());
        }
        return sCrimeLab;
    }
    //返回数组列表,新建的arraylist将内含用户自建的crime,既可以存入也可从中间调取
    public ArrayList<Crime> getCrime(){
        return mCrimes;
    }
    //返回带有指定id的crime对象
    public Crime getCrime(UUID id){
        for (Crime c : mCrimes) {
            if(c.getId().equals(id)){
                return c;
            }
        }
        return null;
    }
    public void addCrime(Crime c){
        mCrimes.add(c);
    }
    public void deleteCrime(Crime c){
        mCrimes.remove(c);
    }
    public boolean saveCrimes(){
        try {
            mSerializer.saveCrimes(mCrimes);
            Log.d(TAG, "crimes saved to file");
            return true;
        } catch (Exception e) {
            // TODO: handle exception
            Log.e(TAG, "error saving crimes",e);
            return false;
        }
    }
}

四、CrimeListFragment.java

ListFragment默认实现方法生成一个全屏listview布局,通过listvie将列表展示给用户,覆盖oncreateview方法可以添加更多高级功能。

package com.dw.criminalintent;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LayoutAnimationController;
import android.view.animation.TranslateAnimation;
import android.webkit.WebView.FindListener;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;

/**
 * 
 * @author dw
 * 
 */
public class CrimeListFragment extends ListFragment {
    private static final int REQUEST_CRIME = 1;
    private ArrayList<Crime> mCrimes;
    private boolean mSubtitleVisible;
    private final static String TAG = "CrimeListFragment";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);// 右上角选项菜单
        // 将显示在操作栏的文字替换为传入的字符串资源中设定的文字,设置托管Crimelistfragment的activity标题
        // getactivity方法不仅可以返回托管activity还允许fragment处理更多的activity相关的事务
        getActivity().setTitle(R.string.crime_title_label);
        // 先获取单例,在获取其中的crime
        mCrimes = CrimeLab.get(getActivity()).getCrime();
        /*
         * ArrayAdapter<Crime> adapter=new ArrayAdapter<Crime>(getActivity(),
         * android.R.layout.simple_list_item_1, mCrimes);
         */
        CrimeAdapter adapter = new CrimeAdapter(mCrimes);

        setListAdapter(adapter);
        // 可以为CrimeListFragment管理内置Listview的adapter

        setRetainInstance(true);
        mSubtitleVisible = false;
    }

    // 无论是单机硬按键还是软键盘,或者是手指的触摸,都会触发该方法
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // 使adapter返回被点击的列表项所对应的crime对象
        Crime c = ((CrimeAdapter) getListAdapter()).getItem(position);
        Log.d(TAG, c.getTitle() + "was clicked");
        //从crimeListFragment中启动Crime明细页,这里用的是显式intent
        //intent构造方法所需的context对象使用的是getActivity方法传入它的托管activity来满足
        Intent i = new Intent(getActivity(), CrimePagerActivity.class);
        //将crimeId的值附加到extra上,可以告知crimefragment应该显示的crime
        i.putExtra(CrimeFragment.EXTRA_CRIME_ID, c.getId());
        startActivityForResult(i, REQUEST_CRIME);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CRIME) {
            // handle result
        }
    }

    // crimelistactivity恢复运行后操作系统会向它发出调用onresume的指定,接到指令后,
    //它的fragmentmanager会调用当前被activity托管的fragment的onresume方法,要保证fragment视图得到刷新,在这里刷新是最安全的选择
    @Override
    public void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        ((CrimeAdapter) getListAdapter()).notifyDataSetInvalidated();// 刷新列表
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // TODO Auto-generated method stub
        super.onCreateOptionsMenu(menu, inflater);
        //将菜单文件的资源id传入并将文件中定义的菜单项目填充到menu实例中
        inflater.inflate(R.menu.fragment_crime_list, menu);

        MenuItem showSubtitle = menu.findItem(R.id.menu_item_show_subtitle);
        if (mSubtitleVisible && showSubtitle != null) {
            showSubtitle.setTitle(R.string.hide_subtitle);
        }
    }
//点击选项菜单中的菜单项时,fragment会收到该方法的回调请求
    @TargetApi(11)
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // TODO Auto-generated method stub
        switch (item.getItemId()) {
        case R.id.menu_item_new_crime:
            Crime crime = new Crime();
            CrimeLab.get(getActivity()).addCrime(crime);
            Intent i = new Intent(getActivity(), CrimePagerActivity.class);
            i.putExtra(CrimeFragment.EXTRA_CRIME_ID, crime.getId());
            startActivityForResult(i, 0);
            return true;
            //实现菜单项标题与子标题的联动显示
        case R.id.menu_item_show_subtitle:
            if (getActivity().getActionBar().getSubtitle() == null) {
                getActivity().getActionBar().setSubtitle(R.string.subtitle);
                mSubtitleVisible = true;
                item.setTitle(R.string.hide_subtitle);
            } else {
                getActivity().getActionBar().setSubtitle(null);
                mSubtitleVisible = false;
                item.setTitle(R.string.show_subtitle);
            }
            return true;
            //若菜单项id不存在,默认的超类版本方法会被调用
        default:
            return super.onOptionsItemSelected(item);// 若ID项不存在,会调用默认的超类版本方法
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v,
            ContextMenuInfo menuInfo) {
        // 用菜单资源文件中定义的菜单项填充菜单实例
        getActivity().getMenuInflater().inflate(R.menu.crime_list_item_context,
                menu);
    }

    // 监听上下文菜单选择事件
    @Override
    public boolean onContextItemSelected(MenuItem item) {
        // TODO Auto-generated method stub
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
                .getMenuInfo();
        int positon = info.position;
        CrimeAdapter adapter = (CrimeAdapter) getListAdapter();
        Crime crime = adapter.getItem(positon);

        switch (item.getItemId()) {
        case R.id.menu_item_delete_crime:
            CrimeLab.get(getActivity()).deleteCrime(crime);
            adapter.notifyDataSetChanged();
            return true;
        }
        return super.onContextItemSelected(item);
    }

    @TargetApi(11)
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent,
            Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        //View v = super.onCreateView(inflater, parent, savedInstanceState);
        View v=inflater.inflate(R.layout.empty_crime, null);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            //根据mSubtitleVisible的值确定是否要设置子标题
            if (mSubtitleVisible) {
                getActivity().getActionBar().setSubtitle(R.string.subtitle);
            }
        }
        ListView listView = (ListView) v.findViewById(android.R.id.list);
        //显示空视图
        if(mCrimes==null){
            listView.setEmptyView(v.findViewById(android.R.id.empty));
        }

        //给listview添加动画效果
        AnimationSet set =new AnimationSet(true);
        Animation animation=new AlphaAnimation(0.0f, 1.0f);
        animation.setDuration(100);
        set.addAnimation(animation);
        animation=new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF,
                0.0f,Animation.RELATIVE_TO_SELF , -1.0f, Animation.RELATIVE_TO_SELF, 0.0f);
        LayoutAnimationController controller=new LayoutAnimationController(set, 0.5f);
        listView.setLayoutAnimation(controller);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            registerForContextMenu(listView);// 登记菜单列表项,长按删除crime
        } else {
            // 多选delete操作
            listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
            listView.setMultiChoiceModeListener(new MultiChoiceModeListener() {
                @Override
                public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                    // not used
                    return false;
                }

                @Override
                public void onDestroyActionMode(ActionMode arg0) {

                }
                @Override
                public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                    MenuInflater inflater = mode.getMenuInflater();
                    inflater.inflate(R.menu.crime_list_item_context, menu);
                    return true;
                }
                @Override
                public boolean onActionItemClicked(ActionMode mode,
                        MenuItem item) {
                    switch (item.getItemId()) {
                    case R.id.menu_item_delete_crime:
                        CrimeAdapter adapter = (CrimeAdapter) getListAdapter();
                        CrimeLab crimeLab = CrimeLab.get(getActivity());
                        for (int i = adapter.getCount() - 1; i >= 0; i--) {
                            if (getListView().isItemChecked(i)) {
                                crimeLab.deleteCrime(adapter.getItem(i));
                            }
                        }
                        mode.finish();
                        adapter.notifyDataSetChanged();
                        return true;
                    default:
                        return false;
                    }
                }

                @Override
                public void onItemCheckedStateChanged(ActionMode mode,
                        int position, long id, boolean checked) {
                    // not used
                }
            });
        }
        return v;
    }

    // 添加定制的内部类
    private class CrimeAdapter extends ArrayAdapter<Crime> {
        // 调用超类的构造方法来绑定crime对象的数组列表
        public CrimeAdapter(ArrayList<Crime> crimes) {
            // 参数0表示不使用预定义布局
            super(getActivity(), 0, crimes);
            // TODO Auto-generated constructor stub
        }
        // 当上下滚动列表时,listview调用getView方法,按需获取要显示的视图;创建并返回定制列表项,填充对应的crime数据
        //首先检查传入的视图对象是否是复用对象,若不是则从定制布局里产生一个新的视图对象。
        @SuppressLint("SimpleDateFormat")
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = getActivity().getLayoutInflater().inflate(
                        R.layout.list_item_crime, null);
            }
            //不管是新对象还是复用对象,都调用该方法获取当前position的crime对象
            Crime c = getItem(position);
            TextView titleTextView = (TextView) convertView
                    .findViewById(R.id.crime_list_item_titleTextView);
            titleTextView.setText(c.getTitle());
            TextView dateTextView = (TextView) convertView
                    .findViewById(R.id.crime_list_item_dateTextView);
            // 格式化时间
            SimpleDateFormat simpleFormatDisplay = new SimpleDateFormat(
                    "yyyy年MM月dd日 E HH:mm:ss");
            dateTextView.setText(simpleFormatDisplay.format(c.getDate()));

            //出现在列表项布局内的任何可聚焦组件(checkbox或button)都应设置为非聚焦状态focusable=false
            CheckBox solvedCheckBox = (CheckBox) convertView
                    .findViewById(R.id.crime_list_item_solvedCheckBox);
            solvedCheckBox.setChecked(c.isSolved());
            return convertView;
        }
    }
}

五、CrimePagerActivity.java

明细视图相关的类,用以托管crimeFragment。

package com.dw.criminalintent;

import java.util.ArrayList;
import java.util.UUID;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
/**
 * 
 * @author dw
 *
 */
public class CrimePagerActivity extends FragmentActivity {
    //viewpager默认加载当前屏幕上的列表项,以及左右相邻页面的数据,从而实现页面滑动的快速切换;默认只显示pageradapter中的第一个列表项。
    private ViewPager mViewPager;
    private ArrayList<Crime> mCrimes;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        //以代码的方式创建内容视图
        mViewPager=new ViewPager(this);
        mViewPager.setId(R.id.viewPager);
        setContentView(mViewPager);
        //从crimelab中获取数据集
        mCrimes=CrimeLab.get(this).getCrime();
        //获取activity 的fm实例
        FragmentManager fm=getSupportFragmentManager();
        //设置adpter为FragmentStatePagerAdapter的一个匿名实例,负责管理与viewpager的对话协同工作
        mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
            @Override
            public int getCount() {
                // TODO Auto-generated method stub
                return mCrimes.size();
            }
            //调用该方法获取crime数组指定位置的crime时,会返回一个已配置的用于显示指定位置crime信息的crimeFragment
            @Override
            public Fragment getItem(int position) {
                // TODO Auto-generated method stub
                Crime crime=mCrimes.get(position);
                return CrimeFragment.newInstance(crime.getId());
            }
        });
        //监听viewpager当前显示页面的状态变化,页面状态变化时可将Crime实例的标题设置给该activity的标题;
        mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
            //当前选择的页面
            @Override
            public void onPageSelected(int pos) {
                // TODO Auto-generated method stub
                Crime crime=mCrimes.get(pos);
                if(crime.getTitle()!=null){
                    setTitle(crime.getTitle());
                }
            }
            //通知页面将会滑向哪里
            @Override
            public void onPageScrolled(int pos, float posOffset, int posOffsetPixels) {
            }
            //告知动迁页面所处的状态,滑动、页面滑动入位到完全静止及页面切换完成后的闲置状态
            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });
        //当从CrimePageActivity创建crimefragment 时应调用crimefragment中的newInstance方法,并传入获取的UUID
        UUID crimeId=(UUID) getIntent().getSerializableExtra(CrimeFragment.EXTRA_CRIME_ID);
        //循环检查crime的id,找到所选crime在数组中的位置,解决viewpager默认显示第一项的问题
        for(int i=0;i<mCrimes.size();i++){
            if(mCrimes.get(i).getId().equals(crimeId)){
                mViewPager.setCurrentItem(i);
                break;
            }
        }
    }

}

七、SingleFragmentActivity.java

通过代码的方式将fragment添加到activity中,activity中的fm负责调用fragment的生命周期方法,添加fragment供fm管理时,onAttach,onCreate,onCreateView方法会被调用。

package com.dw.criminalintent;

import android.support.v4.app.FragmentManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
/**
 * 
 * @author dw 
 */
public abstract class SingleFragmentActivity extends FragmentActivity {
    protected abstract Fragment createFragment();//抽象方法

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        //设置从activity_fragment布局里生成activity视图,
        setContentView(R.layout.activity_fragment);
        //获取一个fragment交由fm管理,fm管理着fragment事务的回退栈
        FragmentManager fm=getSupportFragmentManager();
        //然后在容器中寻找fm里的fragment,若不存在则创建一个新的fragment并将其添加到容器中
        Fragment fragment=fm.findFragmentById(R.id.fragmentContainer);
        if(fragment==null){
            fragment=createFragment();
            //fragment事务被用来添加、删除、附加分离或替换fragment队列中的fragment,这是使用fragment在运行时组装和重新组装界面的核心方式
            fm.beginTransaction().add(R.id.fragmentContainer, fragment)//第二个参数表示新创建的fragment
                                 .commit();
        }
    }
}

八、CrimeCameraActivity.java

package com.dw.criminalintent;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Window;
import android.view.WindowManager;

public class CrimeCameraActivity extends SingleFragmentActivity {

    @Override
    protected Fragment createFragment() {
        // TODO Auto-generated method stub
        return new CrimeCameraFragment();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        //隐藏操作栏和状态栏
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //全屏
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        super.onCreate(savedInstanceState);
    }

}

九、CriminalIntentJSONSerializer.java

创建和解析JSON数据。

package com.dw.criminalintent;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONTokener;
import android.content.Context;

/**
 *
 * @author dw
 *
 */
public class CriminalIntentJSONSerializer {
    private Context mContext;
    private String mFilename;

    public CriminalIntentJSONSerializer(Context context, String filename) {
        mContext = context;
        mFilename = filename;
    }
//先调用openfileoutput方法获取呕吐葡萄stream对象,然后创建一个outputstreamwriter对象,最后调用它的写入方法写入数据
    public ArrayList<Crime> loadCrimes() throws IOException, JSONException {
        ArrayList<Crime> crimes = new ArrayList<Crime>();
        BufferedReader reader = null;
        try {
            InputStream in = mContext.openFileInput(mFilename);
            reader=new BufferedReader(new InputStreamReader(in));
            StringBuilder jsonString = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                jsonString.append(line);
            }
            JSONArray array = (JSONArray) new JSONTokener(jsonString.toString())
                    .nextValue();
            for (int i = 0; i < array.length(); i++) {
                crimes.add(new Crime(array.getJSONObject(i)));
            }
        } catch (FileNotFoundException e) {
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
        return crimes;
    }

    public void saveCrimes(ArrayList<Crime> crimes) throws IOException {
        JSONArray array = new JSONArray();
        for (Crime c : crimes) {
            try {
                array.put(c.toJSON());
            } catch (JSONException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }

            Writer writer = null;
            try {
                OutputStream out = mContext.openFileOutput(mFilename,
                        Context.MODE_PRIVATE);
                writer = new OutputStreamWriter(out);
                writer.write(array.toString());
            } catch (Exception e) {
                // TODO: handle exception
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        }
    }

}

十、DatePickerFragment.java

该fragment也是由crimepageractivity托管。

package com.dw.criminalintent;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.DatePicker;
import android.widget.DatePicker.OnDateChangedListener;
/**
 *
 * @author dw  
 *
 */
public class DatePickerFragment extends DialogFragment {
    public static final String EXTRA_DATE = "com.dw.criminalintent.date";
    private Date mDate;

    public static DatePickerFragment newInstance(Date date) {
        Bundle args = new Bundle();
        args.putSerializable(EXTRA_DATE, date);
        DatePickerFragment fragment = new DatePickerFragment();
        fragment.setArguments(args);
        return fragment;
    }
//创建dialogFragment,在屏幕上显示dialogFragment时,托管activity的fm会调用此方法
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        mDate = (Date) getArguments().getSerializable(EXTRA_DATE);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(mDate);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        // 以流接口的方式创建alertDialog实例
        View v = getActivity().getLayoutInflater().inflate(
                R.layout.dialog_date, null);

        DatePicker datePicker = (DatePicker) v
                .findViewById(R.id.dialog_date_datePicker);
        datePicker.init(year, month, day, new OnDateChangedListener() {
            //
            @Override
            public void onDateChanged(DatePicker view, int year, int month,
                    int day) {
                // TODO Auto-generated method stub
                mDate = new GregorianCalendar(year, month, day).getTime();
                getArguments().putSerializable(EXTRA_DATE, mDate);
            }
        });
        //以流接口的方式创建一个alertdialog实例
        return new AlertDialog.Builder(getActivity())
                .setView(v)
                .setTitle(R.string.date_picker_title)
                .setPositiveButton(android.R.string.ok,
                        new DialogInterface.OnClickListener() {
                    //响应确定按钮事件,调用sendResult方法传入结果代码,以更新日期数据
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {
                                // TODO Auto-generated method stub
                                sendResult(Activity.RESULT_OK);
                            }
                        }).create();
    }
//创建一个intent将日期数据附加到intent上,最后调用onActivityResult
    private void sendResult(int resultCode) {
        // TODO Auto-generated method stub
        if (getTargetFragment() == null)
            return;
        Intent i = new Intent();
        i.putExtra(EXTRA_DATE, mDate);
        getTargetFragment().onActivityResult(getTargetRequestCode(),
                resultCode, i);
    }
}

十一、ImageFragment.java

package com.dw.criminalintent;

import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

public class ImageFragment extends DialogFragment {
    public static final String EXTRA_IMAGE_PATH="com.dw.criminalintent.image_path";
    private ImageView mImageView;


    //获取文件路径并获取缩小版的图片设置给imageview,最后只要图片不再需要,就主动释放内存
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent,
            Bundle savedInstanceState) {
        mImageView=new ImageView(getActivity());
        String path=(String) getArguments().getSerializable(EXTRA_IMAGE_PATH);
        BitmapDrawable image=PictureUtils.getScaleBitmapDrawable(getActivity(), path);
        mImageView.setImageDrawable(image);
        return mImageView;
    }

    @Override
    public void onDestroyView() {
        // TODO Auto-generated method stub
        super.onDestroyView();
        PictureUtils.cleanImageView(mImageView);
    }

    public static ImageFragment newInstance(String imagePath){
        Bundle args=new Bundle();
        args.putSerializable(EXTRA_IMAGE_PATH, imagePath);

        ImageFragment fragment=new ImageFragment();
        fragment.setArguments(args);
        fragment.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
        return fragment;
    }

}

十二、Photo.java

package com.dw.criminalintent;

import org.json.JSONException;
import org.json.JSONObject;

public class Photo {
    private static final String JSON_FILENAME="filename";
    private String mFilename;
    //创建photo对象
    public Photo(String filename) {
        mFilename = filename;
    }
    //json序列化方法,保存及加载photo数据
    public Photo(JSONObject json) throws JSONException{
        mFilename=json.getString(JSON_FILENAME);
    }
    public JSONObject toJSON() throws JSONException{
        JSONObject json=new JSONObject();
        json.put(JSON_FILENAME, mFilename);
        return json;
    }
    public String getFilename(){
        return mFilename;
    }
}

十三、PictureUtils.java

package com.dw.criminalintent;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.view.Display;
import android.widget.ImageView;

public class PictureUtils {

    @SuppressWarnings("deprecation")
    public static BitmapDrawable getScaleBitmapDrawable(Activity a,String path){
        Display diaplay=a.getWindowManager().getDefaultDisplay();
        float destWidth=diaplay.getWidth();
        float destHeight=diaplay.getHeight();

        //读取硬盘上的图片dimensions
        BitmapFactory.Options options=new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeFile(path, options);

        float srcWidth=options.outWidth;
        float srcHeight=options.outHeight;

        int inSampleSize=1;
        if(srcHeight>destHeight||srcWidth>destWidth){
            if(srcWidth>srcHeight){
                inSampleSize=Math.round(srcHeight/destHeight);
            }else{
                inSampleSize=Math.round(srcWidth/destWidth);
            }
        }
        options=new BitmapFactory.Options();
        options.inSampleSize=inSampleSize;

        Bitmap bitmap=BitmapFactory.decodeFile(path, options);
        return new BitmapDrawable(a.getResources(),bitmap);

    }

    public static void cleanImageView(ImageView imageView){
        if(!(imageView.getDrawable() instanceof BitmapDrawable))
            return;
        //清除该视图的图片以节省存储空间
        BitmapDrawable b=(BitmapDrawable) imageView.getDrawable();
        b.getBitmap().recycle();//释放bitmap占用的原始内存空间
        imageView.setImageDrawable(null);
    }

}

十四、CrimePagerActivity.java

明细视图相关的类,用以托管crimeFragment。

package com.dw.criminalintent;

import java.util.ArrayList;
import java.util.UUID;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
/**
 * 
 * @author dw
 *
 */
public class CrimePagerActivity extends FragmentActivity {
    //viewpager默认加载当前屏幕上的列表项,以及左右相邻页面的数据,从而实现页面滑动的快速切换;默认只显示pageradapter中的第一个列表项。
    private ViewPager mViewPager;
    private ArrayList<Crime> mCrimes;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        //以代码的方式创建内容视图
        mViewPager=new ViewPager(this);
        mViewPager.setId(R.id.viewPager);
        setContentView(mViewPager);
        //从crimelab中获取数据集
        mCrimes=CrimeLab.get(this).getCrime();
        //获取activity 的fm实例
        FragmentManager fm=getSupportFragmentManager();
        //设置adpter为FragmentStatePagerAdapter的一个匿名实例,负责管理与viewpager的对话协同工作
        mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
            @Override
            public int getCount() {
                // TODO Auto-generated method stub
                return mCrimes.size();
            }
            //调用该方法获取crime数组指定位置的crime时,会返回一个已配置的用于显示指定位置crime信息的crimeFragment
            @Override
            public Fragment getItem(int position) {
                // TODO Auto-generated method stub
                Crime crime=mCrimes.get(position);
                return CrimeFragment.newInstance(crime.getId());
            }
        });
        //监听viewpager当前显示页面的状态变化,页面状态变化时可将Crime实例的标题设置给该activity的标题;
        mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
            //当前选择的页面
            @Override
            public void onPageSelected(int pos) {
                // TODO Auto-generated method stub
                Crime crime=mCrimes.get(pos);
                if(crime.getTitle()!=null){
                    setTitle(crime.getTitle());
                }
            }
            //通知页面将会滑向哪里
            @Override
            public void onPageScrolled(int pos, float posOffset, int posOffsetPixels) {
            }
            //告知动迁页面所处的状态,滑动、页面滑动入位到完全静止及页面切换完成后的闲置状态
            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });
        //当从CrimePageActivity创建crimefragment 时应调用crimefragment中的newInstance方法,并传入获取的UUID
        UUID crimeId=(UUID) getIntent().getSerializableExtra(CrimeFragment.EXTRA_CRIME_ID);
        //循环检查crime的id,找到所选crime在数组中的位置,解决viewpager默认显示第一项的问题
        for(int i=0;i<mCrimes.size();i++){
            if(mCrimes.get(i).getId().equals(crimeId)){
                mViewPager.setCurrentItem(i);
                break;
            }
        }
    }
}

整个Demo写下来,几乎覆盖到了Android开发的所有知识点,收获还是蛮大的,更多细节的东西后续慢慢再完善。
最后附上源码下载地址:CriminalTucao

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值