1. 程式人生 > >仿微信使用者反饋功能實現

仿微信使用者反饋功能實現

現在的專案要增加一個使用者反饋功能,由於是臨時提出的需求也沒有UI設計,就想到參照微信的設計來實現。
先看微信的效果:
這裡寫圖片描述
在web端實現的,開始也想過做在web端這樣更靈活,但目前的架構還是傳統的純native應用,這麼搞太麻煩,估計要搗鼓一段時間,還是就用android端實現。

功能分析

使用者點選新增圖片按鈕後在手機圖片庫中選擇照片,選定後展示出來,最多選擇4張,水平排列,一行不夠排兩行,就想到GridView結合startActivityForResult+ 執行緒池來實現。因為是上傳圖片,最好轉變為webp格式,再加上畫素壓縮處理,可以大大的節省使用者流量。

流程:
輸入文字->startActivityForResult選擇照片->onActivityResult中更新GridView->點選提交->彈出等待提示框->建立檔案List,文字儲存為txt格式,從GridView中取出圖片儲存為webp格式->執行緒池上傳檔案->上傳結束關閉等待提示框。

功能實現

想起來簡單做起來的時候才發現很多問題,上傳圖片時想彈出一個含旋轉動畫的等待提示框(繼承DialogFragment),每個執行緒執行完後得到執行結果,根據結果設定等待提示框的文字,採用ExecutorService.submit() + future.get()實現時發現一個奇怪的現象:從點選提交到上傳結束沒有看到等待提示框,後來一步步排查問題發現,ExecutorService.submit()之後就會進入future.get(),get()方法會阻塞UI執行緒,導致無法彈出提示框;當執行緒池結束才回到UI執行緒,此時會先處理之前的任務(彈出等待提示框),但由於執行緒池結束又呼叫了DialogFragment的dissmiss()方法,等待提示框很快出現又很快消失了。
好吧,由於沒有搞懂執行緒池浪費了大把時間,程式碼也白寫了,但也讓我對執行緒池加深了認識,這一特性大概幾年都不會忘了,於是又重新考慮實現方式。

最終的實現方式是自定義一個執行緒池,用一個迴圈執行Runnable。
自定義執行緒池:

    class MyThreadPool extends ThreadPoolExecutor {

        private List<File> fileList;
        private MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                             BlockingQueue<Runnable> workQueue, List<File> files) {
            super
(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); fileList = files; } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); } @Override protected void terminated() { super.terminated(); //當呼叫shutDown()或者shutDownNow()時會觸發該方法,關掉等待框 waitingDialog.dismissAllowingStateLoss(); for (File file:fileList){ deleteOldLogcatFile(getApplicationContext(), file.getName()); } //必須用looper才能線上程中用toast Looper.prepare(); if (uploadSucc){ Toast.makeText(getApplicationContext(), "上傳完成", Toast.LENGTH_LONG).show(); } else{ Toast.makeText(getApplicationContext(), "上傳失敗!", Toast.LENGTH_LONG).show(); } Looper.loop(); } }

自定義的好處是線上程池結束後可以自定義提示資訊,在terminated()中刪除了舊檔案,並根據每個執行緒結果提示上傳成功還是失敗。
呼叫自定義執行緒

MyThreadPool myThreadPool = new MyThreadPool(2, 4, 1,
                TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(fileCount), files);
        for (int i = 0; i < fileCount; i++) {
            final File file = files.get(i);
            Runnable runnable = new Runnable(){
                @Override
                public void run() {
                    int ret = ftpUpload(ftpUrl, fptPort, ftpUserName, ftpPassword, ftpRemotePath, path, file.getName());
                    ECMLog.i_ui(CLASS_TAG, "ftpUpload ret: " + ret);
                    uploadSucc = ret == FTP_UPLOAD_SUCC;
                }
            };
            myThreadPool.execute(runnable);
        }

        myThreadPool.shutdown();

還有個效果是點選選擇好的圖片後放大顯示圖片,圖片下方有個刪除圖示能刪除圖片,這也是通過startActivityForResult實現,在onActivityResult中刪除GridView中對應圖片。

圖片處理

startActivityForResult要用兩次,一次是新增圖片,一次是點選已新增的圖片然後重新啟動一個activity顯示圖片,在這個activity中能刪除已選擇的圖片。

圖片的壓縮處理:onActivityResult拿到圖片uri,根據uri得到路徑,通過路徑加上畫素壓縮演算法得到bitmap,再呼叫bitmap.compress()得到webp格式的圖片檔案。

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK && requestCode == REQUEST_ADD_PIC) {
            Uri uri = data.getData();

            String picPath = Utils.getFilePathFromUri(getApplicationContext(), uri);
            if (picPath == null){
                ContentResolver cr = this.getContentResolver();
                try {
                    mBitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }else {
                mBitmap = Utils.getSmallBitmap(picPath, 480, 800);
            }

            //加到第一個
            mGridViewUris.add(0, uri);
            mGridViewDatas.add(0, mBitmap);

            mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
            mGridView.setAdapter(mGridViewAdapter);
            mGridViewAdapter.notifyDataSetChanged();

        }else if (resultCode == RESULT_OK && requestCode == REQUEST_SHOW_PIC){
            int position = data.getIntExtra(PIC_POSITION, 0);
            mGridViewDatas.remove(position);
            mGridViewUris.remove(position);
            mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
            mGridView.setAdapter(mGridViewAdapter);
            mGridViewAdapter.notifyDataSetChanged();
        }

        //包含新增圖片
        int totalPictures = mGridViewDatas.size();
        currentPicTV.setText(String.valueOf(totalPictures - 1));
    }

public static String getFilePathFromUri(final Context context, final Uri uri) {
//        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
        // DocumentProvider
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (DocumentsContract.isDocumentUri(context, uri)) {
                // ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    if ("primary".equalsIgnoreCase(type)) {
                        return Environment.getExternalStorageDirectory() + "/" + split[1];
                    }
                }
                // DownloadsProvider
                else if (isDownloadsDocument(uri)) {
                    final String id = DocumentsContract.getDocumentId(uri);
                    final Uri contentUri = ContentUris.withAppendedId(
                            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
                    return getDataColumn(context, contentUri, null, null);
                }
                // MediaProvider
                else if (isMediaDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    Uri contentUri = null;
                    if ("image".equals(type)) {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                    } else if ("video".equals(type)) {
                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                    } else if ("audio".equals(type)) {
                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                    }
                    final String selection = "_id=?";
                    final String[] selectionArgs = new String[] { split[1] };
                    return getDataColumn(context, contentUri, selection, selectionArgs);
                }
            } else if ("content".equalsIgnoreCase(uri.getScheme())) {
                // Return the remote address
                if (isGooglePhotosUri(uri))
                    return uri.getLastPathSegment();

                return getDataColumn(context, uri, null, null);
            } else if ("file".equalsIgnoreCase(uri.getScheme())) {
                return uri.getPath();
            }
        }
        return null;
    }

//根據路徑獲得圖片並壓縮,返回bitmap用於顯示
    public static Bitmap getSmallBitmap(String filePath, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
//        options.inSampleSize = calculateInSampleSize(options, 480, 800);
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(filePath, options);
    }

    //計算圖片的縮放值
    private static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            final int heightRatio = Math.round((float) height/ (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

通過以上方式,一個截圖檔案原始大小300KB,被壓縮到10KB。

下面是完整程式碼
自定義GridView介面卡:

package com.cetcs.ecmapplication.innerclass;

import android.app.Application;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.cetcs.ecmapplication.R;
import com.cetcs.ecmcommon.ECMLog;
import com.cetcs.ecmcommon.GlobalData;

import java.util.List;


public class GridViewAdapter extends BaseAdapter {
    private String CLASS_TAG = getClass().getCanonicalName();
    private List<Bitmap> mList;
    LayoutInflater mLayoutInflater;
    private ImageView mImageView;
    private Application mAppllication;
    private int mSize;

    public GridViewAdapter(Application application, List<Bitmap> list, int size){
        mAppllication = application;
        mList = list;
        mLayoutInflater = LayoutInflater.from(application);
        mSize = size;
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ECMLog.i_ui(CLASS_TAG, "position: " + position + " mList size: " + mList.size());
        convertView = mLayoutInflater.inflate(R.layout.gridviewlayout, null);
        mImageView = (ImageView) convertView.findViewById(R.id.ItemImage);
        mImageView.setImageBitmap(mList.get(position));
        GlobalData globalData = (GlobalData) mAppllication;
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mImageView.getLayoutParams();
        layoutParams.height = 240 * globalData.mScreenHeight / 1920;
        layoutParams.width = 240 * globalData.mScreenWidth / 1080;
        mImageView.setLayoutParams(layoutParams);
        return convertView;
    }
}

gridviewlayout.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:paddingBottom="4dip" android:layout_width="fill_parent">
    <ImageView
        android:layout_height="wrap_content"
        android:id="@+id/ItemImage"
        android:layout_width="wrap_content"
        android:layout_centerHorizontal="true">
    </ImageView>
</RelativeLayout>

activity的layout
activity_feedback.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:RoundCornerTextView="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_feedback"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="#f0f0f0"
    tools:context="com.cetcs.ecmapplication.FeedbackActivity">

    <TextView
        android:id="@+id/titleTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="反饋和意見"
        android:layout_marginStart="10dp"
        android:layout_marginBottom="10dp"
        />

    <RelativeLayout
        android:id="@+id/textviewLayout"
        android:layout_below="@id/titleTV"
        android:layout_width="match_parent"
        android:background="@color/white"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/feedbackET"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="請填寫10個以上的問題描述"
            android:background="@color/white"
            android:paddingStart="10dp"
            android:paddingEnd="10dp" />

        <TextView
            android:id="@+id/allowedTV"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="10dp"
            android:text="/200"/>

        <TextView
            android:id="@+id/currentNumberTV"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_toStartOf="@id/allowedTV"
            android:text="0"/>
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/pictureLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:layout_below="@id/textviewLayout">

        <TextView
            android:id="@+id/pictureTV"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:textColor="@color/black"
            android:text="圖片(問題截圖,選填)"/>

        <TextView
            android:id="@+id/allowedPicTV"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="10dp"
            android:text="/4"/>

        <TextView
            android:id="@+id/currentPicTV"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toStartOf="@id/allowedPicTV"
            android:text="0"/>

        <GridView
            android:id="@+id/gridview"
            android:layout_marginTop="10dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/pictureTV"
            android:numColumns="3"
            android:verticalSpacing="10dp"
            android:horizontalSpacing="10dp"
            android:columnWidth="90dp"
            android:stretchMode="columnWidth"
            android:gravity="center">

        </GridView>

    </RelativeLayout>

    <TextView
        android:id="@+id/telTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/pictureLayout"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:text="聯絡電話"
        android:layout_marginStart="10dp"
        />

    <EditText
        android:id="@+id/telET"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/telTV"
        android:background="@color/white"
        android:paddingStart="10dp"
        android:paddingEnd="10dp"
        android:hint="選填"/>

    <!--自定義控制元件,圓角button-->
    <acxingyun.cetcs.com.roundconertextview.RoundConerTextView
        android:id="@+id/submitBT"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        RoundCornerTextView:Text="@string/tijiao"
        RoundCornerTextView:BackgroundColor="@color/roundconerunpressed"
        RoundCornerTextView:ConerRadius="5dp"
        RoundCornerTextView:TextColor="@android:color/white"
        RoundCornerTextView:pressedColor="@color/finish_pressed"
        RoundCornerTextView:unPressedColor="@color/finish_unpressed"
        />

</RelativeLayout>

FeedbackActivity

package com.cetcs.ecmapplication;

import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.cetcs.ecmapplication.dialog.WaitingDialog;
import com.cetcs.ecmapplication.innerclass.GridViewAdapter;
import com.cetcs.ecmcommon.ECMActivity;
import com.cetcs.ecmcommon.ECMLog;
import com.cetcs.ecmcommon.GlobalData;
import com.cetcs.ecmcommon.Utils;
import com.cetcs.jniencardmanager.JniCardLoginedInfo;
import com.cetcs.jniencardmanager.JniCardUnloginInfo;
import com.cetcs.jniencardmanager.JniEnCardManager;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import acxingyun.cetcs.com.roundconertextview.RoundConerTextView;

import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static com.cetcs.ecmcommon.Constants.FTP_UPLOAD_SUCC;
import static com.cetcs.logreport.LogcatUploaderUtils.appendStringToFile;
import static com.cetcs.logreport.LogcatUploaderUtils.deleteOldLogcatFile;
import static com.cetcs.logreport.LogcatUploaderUtils.ftpUpload;
import static com.cetcs.logreport.LogcatUploaderUtils.writeStringToFile;

public class FeedbackActivity extends ECMActivity {

    private final String CLASS_TAG = this.getClass().getSimpleName();
    private RelativeLayout textviewLayout;
    private RelativeLayout pictureLayout;
    private EditText telET;
    private TextView currentPicTV;
    private RoundConerTextView submitBT;
    private EditText feedbackET;
    private TextWatcher watcher;
    private TextView currentNumberTV;

    private List<Bitmap> mGridViewDatas;
    private List<Uri> mGridViewUris;
    private GridView mGridView;
    private GridViewAdapter mGridViewAdapter;
    private Bitmap mBitmap;
    private static int REQUEST_ADD_PIC = 1;
    private static int REQUEST_SHOW_PIC = 2;
    private static int MAX_UPLOAD_PIC = 4;
    private static String SHOW_PIC_URI = "show_pic_full_screen";
    private static String PIC_POSITION = "picture_position";

    private WaitingDialog waitingDialog;

    private static final String ftpUrl = xxxxx.xxxxx.xxxxx;
    private static final String fptPort = xxxx;
    private static final String ftpUserName = xxxx;
    private static final String ftpPassword = xxxx;
    private static final String ftpRemotePath = "/aaa/bbb";

    private boolean uploadSucc = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);//取消標題欄
        setContentView(R.layout.activity_feedback);

        initViews();
        initLayoutParameters();
    }

    private void initViews(){
        textviewLayout = (RelativeLayout) findViewById(R.id.textviewLayout);
        pictureLayout = (RelativeLayout) findViewById(R.id.pictureLayout);
        telET = (EditText) findViewById(R.id.telET);
        submitBT = (RoundConerTextView) findViewById(R.id.submitBT);

        feedbackET = (EditText) findViewById(R.id.feedbackET);
        currentNumberTV = (TextView) findViewById(R.id.currentNumberTV);
        //監控輸入字數
        watcher = new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // TODO Auto-generated method stub
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,
                                          int after) {
                // TODO Auto-generated method stub
            }

            @Override
            public void afterTextChanged(Editable s) {
                // TODO Auto-generated method stub
                int len = feedbackET.getText().length();
                ECMLog.i_ui(CLASS_TAG, "onTextChanged len:" + len);
                if (len >= 200){
                    currentNumberTV.setText(String.valueOf(len));
                    currentNumberTV.setTextColor(getResources().getColor(R.color.deep_orange));
                }else {
                    currentNumberTV.setTextColor(getResources().getColor(R.color.black));
                    currentNumberTV.setText(String.valueOf(len));
                }
            }
        };

        feedbackET.addTextChangedListener(watcher);

        mGridView=(GridView) findViewById(R.id.gridview);
        mGridViewDatas = new ArrayList<>();
        mGridViewUris = new ArrayList<>();
        Bitmap addBitmap = Utils.getInstance().readBitmap(getApplicationContext(), R.drawable.add_pic);
        mGridViewDatas.add(0, addBitmap);
        mGridViewUris.add(0, new Uri.Builder().build());
        mGridViewAdapter = new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
        mGridView.setAdapter(mGridViewAdapter);
        mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                int pictureNumber = mGridViewDatas.size();
                ECMLog.i_ui(CLASS_TAG,"position: " + position + " pictureNumber: " + pictureNumber);
                if (position == pictureNumber - 1){
                    if (pictureNumber < MAX_UPLOAD_PIC + 1){
                        chosePictureFromPhone();
                    }
                }else {
                    Uri uri = mGridViewUris.get(position);
                    Intent intent = new Intent(FeedbackActivity.this, PicShowerActivity.class);
                    intent.putExtra(SHOW_PIC_URI, uri.toString());
                    intent.putExtra(PIC_POSITION, position);
                    startActivityForResult(intent, REQUEST_SHOW_PIC);
                }
            }
        });

        submitBT.setOnTouchCallback(new RoundConerTextView.onTouchCallback() {
            @Override
            public void onRoundTextViewTouched() {
                if (feedbackET.getText().length() == 0){
                    Toast.makeText(getApplicationContext(), "請輸入您的建議", Toast.LENGTH_SHORT).show();
                    return;
                }
                if (feedbackET.getText().length() > 200){
                    Toast.makeText(getApplicationContext(), "請輸入200字以內建議", Toast.LENGTH_SHORT).show();
                    return;
                }
                waitingDialog = new WaitingDialog();
                waitingDialog.setCancelable(false);
                waitingDialog.show(getFragmentManager(), getString(R.string.zhengzaishangchaunqingshaohou));

                List<File> list = creatFilesThread();
                if (list == null){
                    waitingDialog.dismissAllowingStateLoss();
                    Toast.makeText(getApplicationContext(), "網路不可用!", Toast.LENGTH_SHORT).show();
                    return;
                }
                ExecutorServiceThread(list);
            }
        });
    }

    private void initLayoutParameters(){
        GlobalData globalDate = (GlobalData) getApplication();

        RelativeLayout.LayoutParams layoutparam = (RelativeLayout.LayoutParams) textviewLayout.getLayoutParams();
        layoutparam.height = 350 * globalDate.mScreenHeight / 1920;
        textviewLayout.setLayoutParams(layoutparam);

        //反饋和意見
        TextView titleTV = (TextView) findViewById(R.id.titleTV);
        titleTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);

        //反饋文字
        feedbackET = (EditText) findViewById(R.id.feedbackET);
        feedbackET.setTextSize(COMPLEX_UNIT_PX, 50 * globalDate.mScreenHeight / 1920);

        //200
        TextView allowedTV = (TextView) findViewById(R.id.allowedTV);
        allowedTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);

        //字數
        TextView currentNumberTV = (TextView) findViewById(R.id.currentNumberTV);
        currentNumberTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);

        layoutparam = (RelativeLayout.LayoutParams) pictureLayout.getLayoutParams();
        layoutparam.height = 430 * globalDate.mScreenHeight / 1920;
        layoutparam.topMargin = 60 * globalDate.mScreenHeight / 1920;
        pictureLayout.setLayoutParams(layoutparam);


        TextView pictureTV = (TextView) findViewById(R.id.pictureTV);
        pictureTV.setTextSize(COMPLEX_UNIT_PX, 46 * globalDate.mScreenHeight / 1920);

        TextView allowedPicTV = (TextView) findViewById(R.id.allowedPicTV);
        allowedPicTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);

        currentPicTV = (TextView) findViewById(R.id.currentPicTV);
        currentPicTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);

        TextView telTV = (TextView) findViewById(R.id.telTV);
        telTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);

        telET = (EditText) findViewById(R.id.telET);
        telET.setTextSize(COMPLEX_UNIT_PX, 50 * globalDate.mScreenHeight / 1920);

        layoutparam = (RelativeLayout.LayoutParams) telET.getLayoutParams();
        layoutparam.height = 128 * globalDate.mScreenHeight / 1920;
        telET.setLayoutParams(layoutparam);

        if (globalDate.getChannelName().equals("westone")){
            submitBT.setBackgroundColor(getResources().getColor(R.color.westonewanchengzhengchang));
            submitBT.setPressedColor(getResources().getColor(R.color.westonewanchengdianji));
            submitBT.setUnPressedColor(getResources().getColor(R.color.westonewanchengzhengchang));
        }

        submitBT.setWidth(990 * globalDate.mScreenWidth / 1080);
        submitBT.setHeight(135 * globalDate.mScreenHeight / 1920);
        submitBT.setTextSize(60 * globalDate.mScreenHeight / 1920);
        submitBT.invalidate();

    }

    private void chosePictureFromPhone(){
        Intent intent = new Intent();
        /* 開啟Pictures畫面Type設定為image */
        intent.setType("image/*");
        /* 使用Intent.ACTION_GET_CONTENT這個Action */
        intent.setAction(Intent.ACTION_GET_CONTENT);
        /* 取得相片後返回本畫面 */
        startActivityForResult(intent, REQUEST_ADD_PIC);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK && requestCode == REQUEST_ADD_PIC) {
            Uri uri = data.getData();

            String picPath = Utils.getFilePathFromUri(getApplicationContext(), uri);
            if (picPath == null){
                ContentResolver cr = this.getContentResolver();
                try {
                    mBitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }else {
                mBitmap = Utils.getSmallBitmap(picPath, 480, 800);
            }

            //加到第一個
            mGridViewUris.add(0, uri);
            mGridViewDatas.add(0, mBitmap);

            mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
            mGridView.setAdapter(mGridViewAdapter);
            mGridViewAdapter.notifyDataSetChanged();

        }else if (resultCode == RESULT_OK && requestCode == REQUEST_SHOW_PIC){
            int position = data.getIntExtra(PIC_POSITION, 0);
            mGridViewDatas.remove(position);
            mGridViewUris.remove(position);
            mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
            mGridView.setAdapter(mGridViewAdapter);
            mGridViewAdapter.notifyDataSetChanged();
        }

        //包含新增圖片
        int totalPictures = mGridViewDatas.size();
        currentPicTV.setText(String.valueOf(totalPictures - 1));
    }

    private List<File> creatUploadFiles(){
        //包含文字、圖片
        List<File> fileList = new ArrayList<>();
        File suggestFile = creatSuggestionFile();
        if (suggestFile != null){
            String tel = telET.getText().toString();
            if (tel.length() > 0){
                tel = "\n" + "tel:" + tel;
                appendStringToFile(getApplicationContext(), suggestFile.getName(), tel);
            }
            fileList.add(suggestFile);
        }

        //不包括新增圖片
        int picCount = mGridViewDatas.size() - 1;

        for (int i = 0;i < picCount;i++){
            //phoneNumber_tfNumber_20170101010101_1
            String bitmapFileName = getFileName() + "_" + i + ".webp";
            Bitmap bitmap = mGridViewDatas.get(i);
            File bitmapFile = saveBitmapToFile(bitmap, bitmapFileName);
            fileList.add(bitmapFile);
        }

        return fileList;
    }

    /**
     * phoneNumber_tfNumber_time
     * 沒有後綴
     * @return
     */
    private String getFileName(){

        String fileName;
        String phoneNumber = "";
        String tfNumber = "";
        String time = "";

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
        time = sdf.format(new Date());

        ..............

        fileName = phoneNumber +"_" + tfNumber +"_" + time;
        return fileName;
    }

    private File creatSuggestionFile(){

        String fileName;
        fileName = getFileName() + ".txt";
        String path = getApplicationContext().getFilesDir().getAbsolutePath();
        File file = new File(path, fileName);
        //儲存String為檔案
        writeStringToFile(getApplication(), feedbackET.getText().toString(), file.getName());
        return file;
    }

    private File saveBitmapToFile(Bitmap bitmap, String fileName){

        String path = getApplicationContext().getFilesDir().getAbsolutePath();
        File file = new File(path, fileName);
        if(file.exists()){
            file.delete();
        }

        FileOutputStream out;
        try{
            out = new FileOutputStream(file);
            if(bitmap.compress(Bitmap.CompressFormat.WEBP, 50, out))
            {
                out.flush();
                out.close();
            }
        }
        catch (FileNotFoundException e)
        {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return file;
    }

    private List<File> creatFilesThread(){
        List<File> fileList = null;
        ExecutorService cacheThreadExecutor = Executors.newSingleThreadExecutor();
        Future<List<File>> future = cacheThreadExecutor.submit(new preUploadTask());

        try {
            fileList = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return fileList;
    }

    private void ExecutorServiceThread(List<File> files) {

        final String path = getApplicationContext().getFilesDir().getAbsolutePath();
        int fileCount = files.size();
        MyThreadPool myThreadPool = new MyThreadPool(2, 4, 1,
                TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(fileCount), files);
        for (int i = 0; i < fileCount; i++) {
            final File file = files.get(i);
            Runnable runnable = new Runnable(){
                @Override
                public void run() {
                    int ret = ftpUpload(ftpUrl, fptPort, ftpUserName, ftpPassword, ftpRemotePath, path, file.getName());
                    ECMLog.i_ui(CLASS_TAG, "ftpUpload ret: " + ret);
                    uploadSucc = ret == FTP_UPLOAD_SUCC;
                }
            };