仿微信用户反馈功能实现
来源:互联网 发布:淘宝售后服务内容 编辑:程序博客网 时间:2024/05/21 19:45
现在的项目要增加一个用户反馈功能,由于是临时提出的需求也没有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; } }; myThreadPool.execute(runnable); } myThreadPool.shutdown(); } class preUploadTask implements Callable<List<File>>{ @Override public List<File> call() throws Exception { if (!Utils.ping()){ return null; } return creatUploadFiles(); } } 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(); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return super.onKeyDown(keyCode, event); } @Override protected void onDestroy() { if (mBitmap != null &&!mBitmap.isRecycled()){ mBitmap.recycle(); mBitmap = null; } mGridView = null; super.onDestroy(); }/** * 通过ftp上传文件 * @param url ftp服务器地址 如: 192.168.1.110 * @param port 端口如 : 21 * @param username 登录名 * @param password 密码 * @param remotePath 上到ftp服务器的磁盘路径 * @param fileNamePath 要上传的文件路径 * @param fileName 要上传的文件名 * @return */ public static int ftpUpload(String url, String port, String username,String password, String remotePath, String fileNamePath,String fileName) { ECMLog.i_ecm(CLASS_TAG, "ftpUpload called,fileName: " + fileName); FTPClient ftpClient = new FTPClient(); FileInputStream fis = null; int result = 0; try { ftpClient.setConnectTimeout(5 * 1000); ftpClient.connect(url, parseInt(port)); ECMLog.i_ecm(CLASS_TAG, "connected..."); boolean loginResult = ftpClient.login(username, password); int returnCode = ftpClient.getReplyCode(); ECMLog.i_ecm(CLASS_TAG, "ftp returnCode: " + returnCode); if (loginResult && FTPReply.isPositiveCompletion(returnCode)) {// 如果登录成功 ftpClient.makeDirectory(remotePath); // 设置上传目录 ftpClient.changeWorkingDirectory(remotePath); ftpClient.setBufferSize(1024); if (fileName.endsWith(".png") || fileName.endsWith(".webp") || fileName.endsWith(".jpeg")){ //上传上去的图片数据格式()一定要写这玩意,不然在服务器就打不开了 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); } ftpClient.setControlEncoding("UTF-8"); ftpClient.enterLocalPassiveMode(); fis = new FileInputStream(fileNamePath + "/" + fileName); ftpClient.storeFile(fileName, fis); result = FTP_UPLOAD_SUCC; //上传成功 } else {// 如果登录失败 result = FTP_LOGIN_ERR; } } catch (IOException e) { ECMLog.e_ecm(CLASS_TAG, "ftp err..."); e.printStackTrace(); result = FTP_CLIENT_ERR;// throw new RuntimeException("FTP客户端出错!", e); } finally { try { ftpClient.disconnect(); } catch (IOException e) { e.printStackTrace(); } } return result; }}
展示图片的activity,PicShowerActivity:
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.view.View;import android.view.Window;import android.widget.ImageView;import android.widget.RelativeLayout;import com.cetcs.ecmapplication.dialog.ActionConfirmActivity;import com.cetcs.ecmcommon.Constants;import com.cetcs.ecmcommon.ECMActivity;import com.cetcs.ecmcommon.ECMLog;import com.cetcs.ecmcommon.GlobalData;import com.cetcs.ecmcommon.Utils;import java.io.FileNotFoundException;import static com.cetcs.ecmcommon.Constants.ACTIONCONFIRM_ACTIVITY_DELETE_PIC;public class PicShowerActivity extends ECMActivity { private ImageView mShowedIV; private ImageView mDeleteIV; private static String SHOW_PIC_URI = "show_pic_full_screen"; private final String CLASS_TAG = this.getClass().getSimpleName(); private static String PIC_POSITION = "picture_position"; private int picPosition; private Bitmap mBitmap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ECMLog.i_ui(CLASS_TAG, "onCreate called..."); requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏 setContentView(R.layout.activity_pic_shower); initViews(); initLayoutParameters(); } private void initViews(){ mShowedIV = (ImageView) findViewById(R.id.mShowedIV); mDeleteIV = (ImageView) findViewById(R.id.mDeleteIV); Intent intent = getIntent(); if (intent != null){ //position picPosition = intent.getIntExtra(PIC_POSITION, 0); ECMLog.i_ui(CLASS_TAG, "picPosition: " + picPosition); //Uri String uriString = intent.getStringExtra(SHOW_PIC_URI); Uri uri = Uri.parse(uriString); mBitmap = null; 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); } mShowedIV.setImageBitmap(mBitmap); } mDeleteIV.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent1 = new Intent(PicShowerActivity.this, ActionConfirmActivity.class); intent1.putExtra(Constants.KEY_INFORMATIONTYPE, ACTIONCONFIRM_ACTIVITY_DELETE_PIC); intent1.putExtra(Constants.KEY_INFORMATION, getString(R.string.quedingshanchutupian)); startActivityForResult(intent1, ACTIONCONFIRM_ACTIVITY_DELETE_PIC); } }); mShowedIV.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setResult(RESULT_CANCELED); finish(); } }); } private void initLayoutParameters(){ GlobalData globalData = (GlobalData) getApplication(); RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mShowedIV.getLayoutParams(); layoutParams.width = 800 * globalData.mScreenWidth / 1080; layoutParams.height = 1400 * globalData.mScreenHeight / 1920; mShowedIV.setLayoutParams(layoutParams); layoutParams = (RelativeLayout.LayoutParams) mDeleteIV.getLayoutParams(); layoutParams.height = 68 * globalData.mScreenHeight / 1920; layoutParams.width = 68 * globalData.mScreenWidth / 1080; mDeleteIV.setLayoutParams(layoutParams); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == ACTIONCONFIRM_ACTIVITY_DELETE_PIC && resultCode == RESULT_OK){ Intent intent = new Intent(); intent.putExtra(PIC_POSITION, picPosition); setResult(RESULT_OK, intent); finish(); } } @Override protected void onDestroy() { if (!mBitmap.isRecycled()){ mBitmap.recycle(); mBitmap = null; } mShowedIV.setImageBitmap(null); mDeleteIV.setImageBitmap(null); super.onDestroy(); }}
最终效果
用户反馈页面:
查看图片:
写这篇文章主要目的还是记录和总结一些开发经验,争取以后每开发一个功能写一篇博客。
- 仿微信用户反馈功能实现
- Android+PHP+Mysql实现用户反馈功能
- 简析 React Native 用户反馈功能实现
- android+json+php+mysql实现用户反馈功能
- android+json+php+mysql实现用户反馈功能
- android+json+php+mysql实现用户反馈功能
- android+json+php+mysql实现用户反馈功能
- 小白虎远程控制软件待实现功能以及用户反馈! 请大家及时反馈需要完善的地方!
- Android 用户反馈界面的实现
- Android后台发送邮件实现用户反馈
- 手机端实现问题反馈功能
- 小程序用户反馈 - HotApp小程序统计仿微信聊天用户反馈组件,开源
- 小程序用户反馈-HotApp小程序统计仿微信聊天用户反馈组件...
- 小程序用户反馈-HotApp小程序统计仿微信聊天用户反馈组件
- 小程序用户反馈 - HotApp小程序统计仿微信聊天用户反馈组件,开源
- 创建MOSS2007 WEB 应用实现服务效果反馈功能
- 用户需求 反馈
- Android,用户反馈
- 背包理论解析
- Bitmap,jpg,png区别以及在Android上实现不载入内存压缩图片
- linux中字符串转换函数 simple_strtoul
- php面试题之五——PHP综合应用(高级部分)
- Linux下massan使用
- 仿微信用户反馈功能实现
- django 实现web接口 python3模拟Post请求
- 课程设计:银行系统的设计与实现
- Dubbo
- (一)SQL基本知识
- spring-boot学习笔记之一——pom
- 结构体指针的常规用法
- Vue.js--基于$.ajax获取数据并与组件的data绑定
- Unity给力插件之ShaderForge(三)