发布帖子和注册功能实现
先做发布帖子的
发布帖子页面布局
布局页面做成这个样子了
1.写布局文件的时候遇到问题
需要改变EditText下滑线的颜色和粗细
改变颜色简单,参考Android 更改EditText下划线的颜色样式
自定义一个样式
<style name="MyEditText" parent="Theme.AppCompat.Light"> <item name="colorControlNormal">#e6e6e6</item> <item name="colorControlActivated">#c6c6c6</item> </style>
在布局中引入
android:theme="@style/MyEditText"
但是这种方法不知道怎么改变下划线的粗细,那试着设置了一下EditText的背景
比如定义一个样式
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape> <solid android:color="#e6e6e6" /> </shape> </item> <item android:bottom="1dp"> <shape> <solid android:color="#Fafafa" /> </shape> </item> </layer-list>
引入样式
android:background="@drawable/bg_line_bottom"
2.处理内容输入EditText
主要是要往里面插入图片。。参考的是实现在edittext中任意插入图片
那篇文章关于打开系统摄像头的方法大概有bug,而且我是在模拟器上测试的,没有摄像头功能,所以还是先去掉拍摄功能,点击上传图片直接打开系统的相册。关于打开摄像头拍照这个之后有时间专门来研究下
post_img.setOnClickListener { var items=Array<CharSequence>(2){""} items[0] = "手机相册" items[1] = "相机拍摄" var dlg=AlertDialog.Builder(this).setTitle("选择图片").setItems(items,object:DialogInterface.OnClickListener{ override fun onClick(dialog: DialogInterface?, which: Int) { when(which){ 0->{ //println("click item 0") var getImage=Intent(Intent.ACTION_GET_CONTENT) getImage.addCategory(Intent.CATEGORY_OPENABLE) getImage.setType("image/*") startActivityForResult(getImage,1) } 1->{ //println("click item 1") //var getImageByCamera= Intent("android.media.action.IMAGE_CAPTURE") //startActivityForResult(getImageByCamera,0) } } } }).create() dlg.show() }
那其实弹出对话框也没有必要了
post_img.setOnClickListener {
var getImage = Intent(Intent.ACTION_GET_CONTENT)
getImage.addCategory(Intent.CATEGORY_OPENABLE)
getImage.type = "image/*"
startActivityForResult(getImage, 1)
}
然后我发现我也没有自定义EditText了,直接在activity中重写onActivityResult方法吧
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode==PHOTO_SUCCESS){
var originUri=data?.data
//创建bitmap
var originBitmap=BitmapFactory.decodeStream(contentResolver.openInputStream(originUri!!))
if(originBitmap!=null){
//设置spannableString
var imageSpan= ImageSpan(this,originBitmap)
var spannableString=SpannableString("[local][local]")//[local]1[local]没啥用
spannableString.setSpan(imageSpan,0,"[local]".length,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
//将图片插入光标位置
val index=post_content.selectionStart
val editText=post_content.editableText
if(index<0||index>=editText.length){
editText.append(spannableString)
}else{
editText.insert(index,spannableString)
}
}else{
println("从相册读取图片失败!!")
}
}
}
还可以新建一个函数设置插入图片的大小。但是好像不设置大小也可以,图片会自适应屏幕的大小
关于SpannableString可以参考
SpannableString和SpannableStringBuilder总结
接下来要考虑怎么把编辑好的帖子发布出去,也主要是处理图片的上传了。关于图片的上传,貌似有两种方法,一种是上传图片的二进制流,另一种是将图片转换成base64编码。。网上有很多介绍图片上传的,我还是不看别的,自己想想办法看
我这里试试把图片转换成base64编码吧
再来看一下这段代码
if(requestCode==PHOTO_SUCCESS){ var originUri=data?.data //创建bitmap var originBitmap=BitmapFactory.decodeStream(contentResolver.openInputStream(originUri!!))if(originBitmap!=null){ //设置spannableString var imageSpan= ImageSpan(this,originBitmap) var spannableString=SpannableString("[local][local]") spannableString.setSpan(imageSpan,0,"[local]".length,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) //将图片插入光标位置 val index=post_content.selectionStart val editText=post_content.editableText if(index<0||index>=editText.length){ editText.append(spannableString) }else{ editText.insert(index,spannableString) } }
貌似我只要把originBitmap转换成base64就可以了,然后再把
var spannableString=SpannableString("[local][local]")
改成
<img src=‘data:image/jpeg;base64,‘+base64ImgData+"‘/>
具体是这样的
var byteData:ByteArray=contentResolver.openInputStream(originUri!!)!!.readBytes() //var base64Encoder= Base64.getEncoder() //var base64ImgString=base64Encoder.encodeToString(byteData) var base64ImgString=Base64.encodeToString(byteData,Base64.NO_WRAP) if(originBitmap!=null){ //设置spannableString var imageSpan= ImageSpan(this,originBitmap) //var spannableString=SpannableString("[local][local]") var imgStr=base64ImgString var spannableStringRes="<img src=‘data:image/jpeg;base64,"+imgStr+"‘/>" var spannableString=SpannableString(spannableStringRes) spannableString.setSpan(imageSpan,0,spannableStringRes.length,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
这样我测试了一下,输出的base64图片编码,是可以直接显示出图片的
3.处理选择分类
就是在当前页面点击“必选”旁边的箭头,跳转到展示所有分类的页面,然后点击分类页面的条目,返回分类给当前的页面。继续用startActivityForResult吧。这个写过太多遍,~每多写一次,布局就变好看一点不知道为什么~代码就不贴了
点击选择之后
4.点击发布按钮,上传到数据库
那似乎要把标题,内容,分类这些数据封装成一个json。我这不想用第三方的工具了,直接建一个类,然后用它的toString方法吧
类似这样吧
class PostMsg { var title:String?=null var content:String?=null var pubdate:Long?=null var category:String?=null var username:String?=null constructor(title:String?,content:String?,pubdate:Long?,category:String?,username:String?){ this.title=title this.content=content this.pubdate=pubdate this.category=category this.username=username }
override fun toString(): String {
return "{title=‘$title‘, content=‘$content‘, category=‘$category‘, username=‘$username‘}"
}
}
kotlin提示convert to primary constructor,转换一下就成这样了
class PostMsg( var title: String?, var content: String?, var pubdate: Long?, var category: String?, var username: String? ) {
override fun toString(): String {
return "{title=‘$title‘, content=‘$content‘, category=‘$category‘, username=‘$username‘}"
}
}
然后设置发布按钮的点击监听
toolbar_action.setOnClickListener { var title=post_title.text.toString() var category=post_category.text.toString() var content=post_content.text.toString() var sp=getSharedPreferences("CurrentUserInfo", Context.MODE_PRIVATE) var username=sp.getString("username","null") var postMsg:PostMsg= PostMsg(title,content,category,username) var client:OkHttpClient= OkHttpClient() var mediaType= "text/html;charset=urf-8".toMediaTypeOrNull() var requestBody=postMsg.toString() var body= requestBody.toRequestBody(mediaType) var request:Request=Request.Builder().post(body).url(url).build() Thread(object:Runnable { override fun run() { var response:Response=client.newCall(request).execute() } }).start() }
当然发送post用的是同步请求,用异步会更好,点击提交,结束当前activity,列表显示帖子正在发布,直到请求完成,显示帖子发布成功;另外返回结果也可以处理一下,但是我已经无力了...
在后端在做一个请求的api
req.setCharacterEncoding("utf-8"); ServletInputStream in =req.getInputStream(); byte[] originBytes=in.readAllBytes(); String originStr=new String(originBytes,"utf-8"); //System.out.println(originStr); JSONObject JsonStr=JSONObject.fromObject(originStr); String title=JsonStr.getString("title"); String content=JsonStr.getString("content"); String category=JsonStr.getString("category"); String username=JsonStr.getString("username"); Timestamp pubdate=new Timestamp(System.currentTimeMillis());
会出现一个错误
net.sf.json.JSONException: Expected a ‘,‘ or ‘}‘ at character 36 of {title=
这意味着帖子内容,里面有一些字符不能转换成json。那我就把content转换成Url编码吧!!!
在客户端编码
var contentUrlEncode=URLEncoder.encode(content,"utf-8")
服务器端解码
content= URLDecoder.decode(content,"utf-8");
还有个问题,是我之前建post表的时候,弄了一个thumb字段,表示缩略图,在后台添加帖子,可以直接上传缩略图,但是客户端发表帖子,貌似要把内容里的第一张图片设置为缩略图
比如,在后台我获得的帖子内容是这样
分享一张图片 <img src=‘data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2w ...........QD0IzUR1W4Mu7djtwKgoojhaMfhig5UTDUrhT8sjc9c81G91K+753+Y5ILU2itVTgndIXKhAWz0pd3PSiiqsith3mf7NHmf7NNoo5UA7zP9mmjgUUUwCiiigAooooAKKKKACiiiszMKKKKACiiigAPWiiiqiVEKKKKooKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Z‘/>啦啦啦
直接保存图片的base64编码貌似也行,但是我又设置了thumb字段的最大长度是100,存不下。所以只好把它转换成二进制流,写入服务器thumb文件夹,并且获得它的相对地址,作为thumb的值
int startPos=content.indexOf("data:image/jpeg;base64,")+"data:image/jpeg;base64,".length(); int endPos=content.indexOf("‘/>"); String base64ImgStr=content.substring(startPos,endPos); //System.out.println(base64ImgStr); byte[] imgBytes= Base64.getDecoder().decode(base64ImgStr); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String ymd = sdf.format(new Date()); String fileName = ymd + ".jpg"; String savePath=req.getSession().getServletContext().getRealPath("/")+"../thumb/"; FileOutputStream out=new FileOutputStream(savePath+fileName); out.write(imgBytes); out.close(); String thumb="/thumb/"+fileName; //System.out.println(thumb);
竟然一次成功了。。。但是这样不安全,要是内容里面本身有“data:image/jpeg;base64,”这样的字符呢?所以其实一开始的时候,或许不应该把图片转成base64编码,而应该直接传图片的二进制流...
最后把值插入数据库就行。。
还有输入检查没有做
运行