1. 程式人生 > >第一份功能較多的安卓專案--紀念日app

第一份功能較多的安卓專案--紀念日app

因為正值跟女朋友在一起的半週年。所以想著做個她需要的app給她用。其實網上同樣功能的app很多,但都有廣告,而且比較龐大,她的手機又比較老卡,所以就根據她的需求來寫,哈哈,算是我的第一個專案經理吧。
首先說一下她的需求:

  1. 倒數日的功能,能夠顯示某個日子離現在還有幾天,提供自動排序的功能
  2. 紀念日的功能,能夠顯示某個日子已經過去了幾天,提供兩種顯示方法,日跟月。
  3. 手電筒的功能
  4. 備註功能。即倒數日中能夠新增備註(未實現)

接下來就說一下實現的過程:
首先是設計介面,先做好下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;
    }

至於新增詳情的程式碼則沒什麼好寫的了。介面也沒有設計。