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模型层
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开发的所有知识点,收获还是蛮大的,更多细节的东西后续慢慢再完善。