本次更新的主要内容为增加分享功能,优化评论内容的显示方式,优化文章内容的显示方式等,下面分别介绍其过程。
一、分享功能的实现
这里的分享主要是文章的分享(虽然之前写的评论界面也是有分享按钮的,但是不论从开发者还是用户角度,我感觉评论的分享并没有什么用)。分享的途径大致可以分成两类:
第一种是注册腾讯的开发者账户,或者用友盟的分享sdk,但是他们都要按照一定的流程,调用他们定制的api接口,按照一定的标准才能分享,而且还要合规,比较繁琐。
第二种就是调用Android集成的分享接口,可以分享图片,链接,文件等,缺点就是界面比较凌乱,没有第三方api的整洁。
这里我采用的是第二种。
首先要确定的是,要分享的是什么,这里可以直接照搬微信分享的样式,比如下面这张图里面:
这个图片上,上面是文章标题,右下角是一个链接的图片,以为是新闻,所以还可以在标题下面写一段正文,所以我就把要分享的内容设定成这样:
之所以有个QR code,是因为我分享的只是图片,不能像微信sdk那样,可以点击图片直接看新闻,这里只能曲线救国,提供一个二维码,将文章的链接写入二维码,然后利用微信的提取二维码功能,就能看到新闻了,这里比微信sdk做法多了一步,不过还是能接受的。下面开始写代码。
在gradle里面,加入一行代码,引入zxing二维码库:
implementation 'cn.yipianfengye.android:zxing-library:2.2'
因为此时的文章浏览页面还没有任何的分享按钮,所以需要在Toolbar上面添加一个分享按钮。在res目录的menu子目录下面新建一个菜单文件article_panel.xml,这里面质只包含一个分享按钮:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item app:showAsAction="always"
android:id="@+id/share_pic"
android:icon="@drawable/ic_share"
android:title="@string/share_article"/>
</menu>
因为这个时候,新闻页面是分成有图片和无图片的新闻来加载的,所以很多代码都要在两个Activity写成一样的,为了简洁一点,这里我添加一个两个新闻页面的共同基类ArticleBase类:
public class ArticleBase extends AppCompatActivity {
}
本来两个新闻页面都是派生自AppCompactActivity的,此时把他们的基类改成ArticleBase:
public class NewsDetail extends ArticleBase {
...
}
public class DetailWithPic extends ArticleBase {
...
}
为了加载刚才创建的菜单,在ArticleBase里面重写onCreateOptionsMenu方法,这样就可以在两个文章页面都加载同样的菜单:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//将包含分享按钮的菜单映射到页面头部的Toolbar
getMenuInflater().inflate(R.menu.article_panel,menu);
return super.onCreateOptionsMenu(menu);
}
然后再添加一个方法,将分享按钮被点击之后的执行的代码封装进去,供两个Activity调用,这个方法的参数分别就是我刚才设定的要分享的二维码,标题和正文:
//分享的方法
public void shareArticle(Bitmap sharePic,String articleTitle,String articleContent){
...
}
增加两个String全局变量,分别保存文章标题和部分正文,还有一个Bitmap,用来保存文章链接的二维码:
//文章标题
protected String shareTitle;
//部分的正文
protected String articleContent = "";
//要分享的图片
protected Bitmap shareRawBitmap;
打开两个Activity,初始化二维码:
//初始化二维码
shareRawBitmap = CodeUtils.createImage(articleUrl,110,110,BitmapFactory.decodeResource(getResources(),R.drawable.guanwang));
(没错,一行代码生成二维码,真是炒鸡方便的)
然后重写分享按钮被点击后的回调函数onOptionsItemSelected:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//判断是否是我们增加的分享按钮,其实此时ToolBar上面只有这一个按钮,所以这里的判断是多余的
//但是不确定以后是否增加其他的按钮,所以先保留判断
if (item.getItemId() == R.id.share_pic){
if (shareRawBitmap != null){
shareArticle(shareRawBitmap,shareTitle,articleContent);
}else Log.d(TAG, "onOptionsItemSelected: 要分享的图片为空,请排查原因");
}
return super.onOptionsItemSelected(item);
}
这里遇到了一个坑,那就是初始化二维码的时候,传入的中心的图片是xml格式的svg图片,但是svg图片解析后在这里是不能正常分辨的,所以这里得到的二维码没有中心的观网标志,这时候把解析的图片改成png或者jpg格式就可以正常解析了。
代码写到这里还只是铺垫,真正的难点在于如何在shareArticle方法内,把标题,正文,和二维码合成到一张图片上面,这里我使用了绘图相关的API方法,将所有的内容画到画布上,然后把画布上的图片分享出去。
shareArticle内部,首先新建一个Bitmap用作画布的底面,所有的内容都将画到这个Bitmap上面:
//新建一张位图,用于分享
Bitmap shareBitmap = Bitmap.createBitmap(410,180,Bitmap.Config.ARGB_8888);
然后是画布和画笔:
//新建画布,参数为上面刚刚创建的位图,使用画布在位图上面创建要分享的内容
Canvas shareCanvas = new Canvas(shareBitmap);
//将要分享的位图缩放到理想的尺寸
Bitmap sharingPic = Bitmap.createScaledBitmap(sharePic,110,110,false);
//新建一个画笔,用于将要分享的内容画到画布上面
Paint sharePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
料都备齐了,下面是作画的过程,首先把底色涂成白色:
//先绘制白色底色
shareCanvas.drawColor(Color.rgb(0xff,0xff,0xff));
然后把二维码画到画布上:
//首先将图片绘制到画布上面的合适位置
shareCanvas.drawBitmap(sharingPic,(float) (shareBitmap.getWidth() - 108) , (float) (shareBitmap.getHeight() * 0.75 - 68) ,sharePaint);
然后是文字,把字体调到合适大小,直接写在画布合适位置:
//将要分享的文章标题绘制到要分享的图片上面,首先应该设置分享文字的字体和颜色
sharePaint.setTextSize(18f);
//然后将文字绘制到画布,有5dp的Margin
int titleLength = articleTitle.length();
shareCanvas.drawText(titleLength >22 ? articleTitle.substring(0,22) : articleTitle,5,23,sharePaint);
标题下面是正文,字体要小一些,而且文章有很多行,所以需要在一个循环内,将文章分批次画到要分享的图片上:
sharePaint.setTextSize(12f);
//创建一个循环,将文字绘制到画布上面
int contentLength = articleContent.length();
//判断内容的行数
int rowNum = contentLength / 25 > 6 ? 6 : contentLength / 25;
for (int i = 0 ; i <= rowNum ; i ++){
int clipEnd = (i + 1) * 25 >= contentLength ? contentLength: (i + 1) * 25;
shareCanvas.drawText(articleContent.substring(i * 25,clipEnd),5,50 + i * 20,sharePaint);
}
将此时已经合成的图片保存在本地(这一步可有可无):
//下面的代码将文件保存到本地
Uri shareLink = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(),shareBitmap,null,null));
String shareUrl = Environment.getExternalStorageDirectory() + "/guancha/";
File guanchaUrl = new File(shareUrl);
if (!guanchaUrl.exists()) guanchaUrl.mkdirs();
String picUrl = shareUrl + String.format("%s.jpg",System.currentTimeMillis());
picUrl = String.format(picUrl, System.currentTimeMillis());
Log.d(TAG, "shareArticle: " + picUrl);
File shareFileUrl = new File(picUrl);
try {
shareFileUrl.createNewFile();
shareBitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(shareFileUrl));
} catch (IOException e) {
e.printStackTrace();
}
存储需要申请存储权限,但是权限申请已经在WelcomeActivity中申请,这里就不在复述了。
最后是调用Android系统的原生分享api,将合成的图片分享出去:
//然后调用Android系统原生的分享接口,分享图片
Intent intent = new Intent(Intent.ACTION_SEND);//action_send为分享的枚举类型
//设置所分享内容的类型
intent.setType("image/*");
//将要分享的图片的uri传入intent
intent.putExtra(Intent.EXTRA_STREAM,shareLink);
Intent shareIntent = Intent.createChooser(intent,"分享文章到:");
//启动分享的页面
startActivity(shareIntent);
调试一下 :
在文件管理里面可以看到,分享的图片是这样的:
然后在微信聊天里长按图片就可以浏览新闻了:
二、优化评论显示方式
因为有关文章的内容,都是用爬虫解析出来的,所以文章也好,评论也好,总会有时候掺杂着一些html的元素,比如说下面这些:
上面两张图片里面的br标签对应于换行,strong标签对应于加粗,img标签对应于表情。
在这里可以借助Android原生的一个文本优化api来对html内容进行格式化,一句代码就可以了:
Html.fromHtml(comment);
这里出现了一个问题,这个只有一个参数的方法,在高版本的Android里面是一个过期的方法,所以要判断一下Android的版本,然后在高版本里面使用此方法的重载方法:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(comment,Html.FROM_HTML_MODE_COMPACT,source -> {
ImageView htmlView = new ImageView(context);
Picasso.get().load(source).placeholder(R.drawable.guanwang).into(htmlView);
htmlDrawable = htmlView.getDrawable();
if (htmlDrawable == null) Log.d(TAG, "formatComment: 图片为空");
return htmlDrawable;
},null);
}else return Html.fromHtml(comment);
重载方法里面需要提供一个flag,然后需要提供一个ImageGetter参数,因为ImageGetter是一个函数式接口,所以这里用的一个lambda表达式生成一个ImageGetter的匿名实现类。
这里面最大的问题是,观网的表情是gif格式的图片,但是Android原生的view是不支持显示gif格式图片的,所以就导致Drawable.createFromStream方法如果传入是gif的流,则解析出错,并且返回null。
因此这里就绕了一个弯,用第三方框架Piccaso,先加载gif图片到一个缓冲ImageView里面,然后从缓冲的ImageView里面得到Drawable,就可以在ImageGetter的方法里面返回了。
调试一下:
可以看到,换行,加粗,表情等问题都解决了。
三、优化文章页面
这是最简单的一步,因为文章内容和评论内容是连成一体的,不太美观,所以我把文章内容和评论分别包含进一个CardView里面,设置一个间隔,然后给一个elevation,这样会有一些错落感。
在布局文件activity_detail_with_pic.xml和activity_news_detail.xml中,添加两个<CardView ... />标签,并且将内容和评论分别剪切进去:
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:background="#ffffffff"
android:elevation="2dp"
app:cardCornerRadius="0dp"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:animateLayoutChanges="true"
android:id="@+id/detail_no_pic_frame"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/news_detail_text"
android:textSize="15sp"
/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:background="#ffffff"
android:elevation="2dp"
android:layout_marginTop="6dp"
app:cardCornerRadius="0dp"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/no_pic_recycler"
/>
<View
android:layout_width="match_parent"
android:layout_height="60dp"/>
</androidx.cardview.widget.CardView>
调试一下,就能看到错落的效果了:
本次更新结束。