第一份功能較多的安卓專案--紀念日app
因為正值跟女朋友在一起的半週年。所以想著做個她需要的app給她用。其實網上同樣功能的app很多,但都有廣告,而且比較龐大,她的手機又比較老卡,所以就根據她的需求來寫,哈哈,算是我的第一個專案經理吧。
首先說一下她的需求:
- 倒數日的功能,能夠顯示某個日子離現在還有幾天,提供自動排序的功能
- 紀念日的功能,能夠顯示某個日子已經過去了幾天,提供兩種顯示方法,日跟月。
- 手電筒的功能
- 備註功能。即倒數日中能夠新增備註(未實現)
接下來就說一下實現的過程:
首先是設計介面,先做好下xml,經驗表明,邊寫功能邊敲程式碼絕對是不對的,因為會很亂。設想有一個登陸介面,介面上是頭像,有圓角的輸入框(自定義),有圓角的按鈕
pw_shape.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:padding="10dp">
<!--solid表示填充顏色,如果想讓輸入框看起來像透明的一樣就讓color跟背景顏色一樣即可-->
<solid
android:color="#FFFFFF"></solid >
<!--corners表示四個角的弧度,注意bottomRightRadius是左下角,left是右下角的弧度-->
<corners
android:bottomRightRadius="15dp"
android:bottomLeftRadius="15dp"
android:topLeftRadius="15dp"
android:topRightRadius="15dp" />
<!--stroke表示描邊,width表示寬度,color為描邊的顏色-->
<stroke
android:width="2dp"
android:color="#FF99CC"/>
<!--padding:距離邊緣的距離-->
<padding
android:left="10dp"/>
</shape>
手電筒在登陸介面。手電筒的程式碼其實挺簡單的。主要就是開啟閃光燈。程式碼如下
lightBtn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
if(!flag){
layout.setBackgroundColor(Color.parseColor("#292421"));
lightBtn.setBackgroundResource(R.drawable.flash_head);
Camera camera = Camera.open();
parameters = camera.getParameters();
parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);// 開啟
camera.setParameters(parameters);
camera.startPreview();
flag=true;
}else{
flag=false;
layout.setBackgroundColor(Color.parseColor("#FFFFFF"));
lightBtn.setBackgroundResource(R.drawable.log_in_head_2);
parameters.setFlashMode(Parameters.FLASH_MODE_OFF);// 關閉
camera.setParameters(parameters);
camera.stopPreview();
camera.release();
camera=null;
}
}
});
開啟的邏輯:
Camera camera = Camera.open();//開啟Camera
Parameters parameters = camera.getParameters(); //獲得引數 parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);// 將閃光燈的模式設為開啟
camera.setParameters(parameters);
camera.startPreview(); //開啟閃關燈
關閉的邏輯:
layout.setBackgroundColor(Color.parseColor("#FFFFFF"));
lightBtn.setBackgroundResource(R.drawable.log_in_head_2);
parameters.setFlashMode(Parameters.FLASH_MODE_OFF);// 關閉
camera.setParameters(parameters);
camera.stopPreview();
camera.release();
camera=null;//*
這裡同上,只需要將parameter設為off,再stopPreview,再release即可。要注意的一點便是:release後要將camera設為null,否則關了再開會報錯,提示camera已經被使用
之後便什麼新東西,新增Button,EditText等。
然後便是紀念日的設計,同樣先設計xml。因為女票要求簡單,只需要介面簡潔,所以就上方自己設計的普通的選單,下面是個自定義的listView
自己定製的選單:
其實就是一層layout。然後在裡面放自己需要的東西。那我們一般的都是上面一橫條,橫條中間是標題,兩邊是按鈕,新建一個LinearLayout。一次放入Button,TextView,Button.
<?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="wrap_content"
android:background="@drawable/date"
>
<Button
android:id="@+id/title_btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="返回"
android:textColor="#fff"/>
<!--要將TextView置中,則要將layout_width置0,layout_weight="1",這樣會將一行分為兩半,其中TextView佔一半,另外兩個button各佔1/4-->
<TextView
android:id="@+id/title_text"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="紀念日"
android:textColor="#fff"
android:textSize="24sp" />
<Button
android:id="@+id/title_btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="新建"
android:textColor="#fff"/>
</LinearLayout>
這個xml有兩點要注意的,就是android:layout_gravity跟android:gravity的區別。
android:gravity是文字在控制元件中的對齊方式,而android:layout_gravity是控制元件在layout中的對齊方式。因為我們這個layout的高度設為wrap_content了,所以就只有按鈕的高度。之後這個xml就可以被其他引用了。只需要將其他layout的標題欄隱藏,然後在xml中引入即可。隱藏標題欄的程式碼:
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//這一句一定要在setContentView前
setContentView(R.layout.activity_count_down);
然後便是listview的設計。因為我是想著左邊一個紀念日的內容,然後右邊一個框裡面有天數,框的左邊有”已經“二字,所以定製list_item.xml
首先將layout的orientation設為”horizontal”
然後將顯示內容的TextView設為android:layout_weight=”1”。這樣顯示出來的效果就是“已經”二字貼著右邊的框。
然後右邊的框因為系統沒有所以我們需要自己定製,其實也簡單,就是一層有背景的layout,然後裡面有一個TextView,設定其layout_gravity為center將其放在中間就可以了。
接著便是新增邏輯,主要是點選所在行可以進行編輯,長按彈出選單可以刪除,搖一搖可以讓框3D旋轉。
Adapter的重寫,需要注意要把框所在的layout也申請個空間,因為字型是在這層layout上更改的。
class ViewHolder {
private LinearLayout countDownLayout;
private TextView content;
private TextView date;
}
private DayAdapter(Context context, ArrayList<Day1> dates) {
this.context = context;
this.dates = dates;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return dates.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return dates.get(arg0);
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder vh;
View view;
if (convertView == null) {
vh = new ViewHolder();
/**
*注意這裡要先inflate自定義的list_item
*/
view = LayoutInflater.from(context).inflate(R.layout.list_item, null);
/**
*注意這裡要給layout一個空間
*/
vh.countDownLayout = (LinearLayout) view.findViewById(R.id.count_down_layout);
vh.content = (TextView) view.findViewById(R.id.count_down_content);
vh.date = (TextView) view.findViewById(R.id.count_down_datetext);
view.setTag(vh);
} else {
view = convertView;
vh = (ViewHolder) view.getTag();
}
vh.content.setText(dates.get(position).content);
int passed = dates.get(position).days;
if (first){
vh.date.setText(passed + "天");
first=false;
}
else {
if (mark == 1) {
applyRotation(vh.countDownLayout, 0, 90);
vh.date.setText(passed + "天");
} else {
applyRotation(vh.countDownLayout, 0, 90);
vh.date.setText(passed + "月");
}
}
return view;
}
}
3D翻轉特效:這個不懂,網上找的程式碼,需要翻轉時呼叫applyRotation(layout,start,end)即可。一般把start設為0,end設為90,因為我們要翻轉的是整個框,所以layout傳入countDownLayout。因為我這個dayadapter中的list的泛型是Day1,其中有int型的一個數字,就是顯示出來的天數。這樣設計是為了能夠讓搖一搖時getView得以被呼叫(list中內容有變化,使用notifyDataSetChanged()才有效。)
private void applyRotation(LinearLayout layout, float start, float end) {
final float centerX = layout.getWidth() / 2.0f;
final float centerY = layout.getHeight() / 2.0f;
final Rotate3dAnimation rotation = new Rotate3dAnimation(start, end, centerX, centerY, 310.f, true);
rotation.setDuration(500);
rotation.setFillAfter(true);
rotation.setInterpolator(new AccelerateInterpolator());
rotation.setAnimationListener(new DisplayNextView(layout));
layout.startAnimation(rotation);
}
private final class DisplayNextView implements Animation.AnimationListener {
private LinearLayout layout;
DisplayNextView(LinearLayout layout) {
this.layout = layout;
}
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation arg0) {
// TODO Auto-generated method stub
layout.post(new SnapViews(layout));
}
}
private final class SnapViews implements Runnable {
private LinearLayout layout;
SnapViews(LinearLayout layout) {
this.layout = layout;
}
public void run() {
final float centerX = layout.getWidth() / 2.0f;
final float centerY = layout.getHeight() / 2.0f;
Rotate3dAnimation rotation = null;
layout.requestFocus();
rotation = new Rotate3dAnimation(90, 0, centerX, centerY, 310.0f, false);
rotation.setDuration(500);
rotation.setInterpolator(new DecelerateInterpolator());
layout.startAnimation(rotation);
}
}
}
之後是搖一搖。就是微信搖一搖的原理。使用手機的加速度感測器來感應是否有搖動。
private SensorManager sensorManager;
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor sensor =sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
/**
*在onSensorChanged中新增程式碼邏輯,我設定兩次搖一搖的時間不能超過4秒
*/
private SensorEventListener listener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
float x = Math.abs(event.values[0]);
float y = Math.abs(event.values[1]);
float z = Math.abs(event.values[2]);
if ((System.currentTimeMillis() - CountDownActivity.currentTime > 4000) && (x > 12 || y > 12 || z > 12)) {
CountDownActivity.currentTime = System.currentTimeMillis();
if (mark == 1) {
mark = 2;
for (Day1 d : dayLists) {
d.days = countDay(d.date, mark);
adapter.notifyDataSetChanged();
}
} else if (mark == 2) {
mark = 1;
for (Day1 d : dayLists) {
d.days = countDay(d.date, mark);
}
adapter.notifyDataSetChanged();
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
彈出選單:首先給選單註冊,然後重寫程式碼即可。add方法的四個引數含義:
menu.add(groupId, itemId, order, title)
groupld 這個選單的組別
itemid 是用來獲取這個指定選單項的.可以定義常量,不容易混,比如final int DELETE=1;
所謂order就是這個組別的第幾項,0為第一項
title 不用說都知道是顯示的標題了
registerForContextMenu(list);
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
menu.add(0, 1, 0, "刪除");
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 1:
Day1 d = (Day1) adapter.getItem(clickId);
Log.e("clickId", " " + clickId);
String content = d.content;
for (Day1 d1 : dayLists) {
if (d1.content.equals(content)) {
dayLists.remove(d1);
break;
}
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("CountDays", "content=?", new String[] { d.content });
adapter.notifyDataSetChanged();
}
return true;
}
至於新增詳情的程式碼則沒什麼好寫的了。介面也沒有設計。