香奈尔耳环高仿哪里卖◆拿货十微信:17188884373◆| 耗时:0.049秒|14925条结果

Android 仿群聊头像

来自: http://blog.csdn.net//lyhhj/article/details/49935345 最近小编搞了一个仿群聊头像的一个功能,分享给大家... 工作中需要实现仿钉钉群头像的一个功能,就是个人的头像拼到一起显示,看了一下市场上的APP好像的群聊头像是组合的,QQ的头像不是,别的好像也没有了。今天给大家分享一下怎么实现的吧。首先我们先看一下效果图: 好了,下面说一下具体怎么实现的: 实现思路 1.首先获取Bitmap图片(本地、网络) 2.创建一个指定大小的缩略图 3.组合Bitmap图片 很简单,本地图片需要我们从本地读取,如果是网络图片我们也可以根据URL来获取bitmap进行组合 具体实现过程 1.布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:gravity="center" android:orientation="vertical" android:background="#987" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <TextView android:background="#fff" android:layout_width="match_parent" android:layout_height="1dp"/> <ImageView android:src="@drawable/j" android:id="@+id/iv1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:background="#fff" android:layout_width="match_parent" android:layout_height="1dp"/> <ImageView android:src="@drawable/j" android:id="@+id/iv2" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:background="#fff" android:layout_width="match_parent" android:layout_height="1dp"/> <ImageView android:src="@drawable/j" android:id="@+id/iv3" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:background="#fff" android:layout_width="match_parent" android:layout_height="1dp"/> <ImageView android:src="@drawable/j" android:id="@+id/iv4" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:background="#fff" android:layout_width="match_parent" android:layout_height="1dp"/> </LinearLayout> 四个ImageView控件,用来显示图片不说了 2.获取Bitmap,设定图片的属性 /** * 获取图片数组实体 * by Hankkin at:2015-11-19 22:00:55 * @param count * @return */ private List<BitmapBean> getBitmapEntitys(int count) { List<BitmapBean> mList = new ArrayList<>(); String value = PropertiesUtil.readData(this, String.valueOf(count), R.raw.data); String[] arr1 = value.split(";"); int length = arr1.length; for (int i = 0; i < length; i++) { String content = arr1[i]; String[] arr2 = content.split(","); BitmapBean entity = null; for (int j = 0; j < arr2.length; j++) { entity = new BitmapBean(); entity.setX(Float.valueOf(arr2[0])); entity.setY(Float.valueOf(arr2[1])); entity.setWidth(Float.valueOf(arr2[2])); entity.setHeight(Float.valueOf(arr2[3])); } mList.add(entity); } return mList; } 3.创建压缩图片,这里我们用到了ThumbnailUtils中的extractThumbnail()方法,参数为bitmap,width,height /** * 初始化数据 * by Hankkin at:2015-11-19 21:59:03 */ private void initData(){ /*获取四个图片数组*/ bitmapBeans1 = getBitmapEntitys(1); bitmapBeans2 = getBitmapEntitys(2); bitmapBeans3 = getBitmapEntitys(3); bitmapBeans4 = getBitmapEntitys(4); /*bitmap缩略图*/ Bitmap[] bitmaps1 = { ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans1 .get(0).getWidth(), (int) bitmapBeans1.get(0).getWidth())}; Bitmap[] bitmaps2 = { ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans2 .get(0).getWidth(), (int) bitmapBeans2.get(0).getWidth()), ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans2 .get(0).getWidth(), (int) bitmapBeans2.get(0).getWidth())}; Bitmap[] bitmaps3 = { ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans3 .get(0).getWidth(), (int) bitmapBeans3.get(0).getWidth()), ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans3 .get(0).getWidth(), (int) bitmapBeans3.get(0).getWidth()), ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans3 .get(0).getWidth(), (int) bitmapBeans3.get(0).getWidth())}; Bitmap[] bitmaps4 = { ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans4 .get(0).getWidth(), (int) bitmapBeans4.get(0).getWidth()), ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans4 .get(0).getWidth(), (int) bitmapBeans4.get(0).getWidth()), ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans4 .get(0).getWidth(), (int) bitmapBeans4.get(0).getWidth()), ThumbnailUtils.extractThumbnail(BitmapUtils.getScaleBitmap( getResources(), R.drawable.j), (int) bitmapBeans4 .get(0).getWidth(), (int) bitmapBeans4.get(0).getWidth())}; } 4.组合bitmap图片(也就是将我们的图片用Canvas画到一起) /** * 获得合在一起的bitmap * @param mEntityList * @param bitmaps * @return */ public static Bitmap getCombineBitmaps(List<BitmapBean> mEntityList, Bitmap... bitmaps) { Bitmap newBitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); for (int i = 0; i < mEntityList.size(); i++) { bitmaps[i] = GetRoundedCornerBitmap(bitmaps[i]); newBitmap = mixtureBitmap(newBitmap, bitmaps[i], new PointF( mEntityList.get(i).getX(), mEntityList.get(i).getY())); } return newBitmap; } 这里我为了好看将图片设置成圆形的了 /** * 获取圆形的bitmap * @param bitmap * @return */ public static Bitmap GetRoundedCornerBitmap(Bitmap bitmap) { try { Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); final Paint paint = new Paint(); final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); final RectF rectF = new RectF(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())); final float roundPx = 50; paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(Color.BLACK); canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); final Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); canvas.drawBitmap(bitmap, src, rect, paint); return output; } catch (Exception e) { return bitmap; } } 最后开画 /** * 画bitmap * @param first * @param second * @param fromPoint * @return */ public static Bitmap mixtureBitmap(Bitmap first, Bitmap second, PointF fromPoint) { if (first == null || second == null || fromPoint == null) { return null; } Bitmap newBitmap = Bitmap.createBitmap(first.getWidth(), first.getHeight(), Bitmap.Config.ARGB_8888); Canvas cv = new Canvas(newBitmap); cv.drawBitmap(first, 0, 0, null); cv.drawBitmap(second, fromPoint.x, fromPoint.y, null); cv.save(Canvas.ALL_SAVE_FLAG); //保存全部图层 cv.restore(); return newBitmap; } 这样就简单的实现了群聊头像的效果,当然需要对图片做一些处理,已防止OOM,你也可以将它自定义成一个View组件,小编有时间的话会实现这个的。 最后再给大家看一下小编项目上实现的效果吧,没啥区别,只不多数据源不一样了,是从网络上获取的。 小编已经把代码上传导github上了,求大家star啊 https://github.com/Hankkin/WeixinGroupIconDemo
http://www.itnose.net/detail/6449387.html 2016-02-02 23:59   [Android]

Android仿头像裁剪

最近公司的APP很多用户反应无法上传头像,于是打算修改原来头像裁剪的代码。参考、QQ、唱吧头像裁剪的操作,决定就仿头像裁剪来上传用户头像,在Android大神鸿洋的一篇仿头像的博客(博客地址结尾会贴出来)的基础加了一些代码,我加的代码主要增加如下的功能: 1.增加对大图的处理,缩放
http://www.itnose.net/detail/6271449.html 2015-05-13 23:48   [Android]

仿多图选择

到了Eclipse上,并对其中的功能进行修改扩展,和图片选择相似度达80%以上,修改后的效果图如下: 其中用到了一个强大的图片加载利器:picasso,解决在快速滑动图片列表的过程中出现OOM现象,反应极快。同时本例中支持预览图片,并可在预览过程中再次进行选择,预览支持图片手势方法
http://www.itnose.net/detail/6259408.html 2015-04-29 17:43   [其它]

仿仿ios版应用源码

仿基本功能。基于XMPP服务器的即时通信以及交友客户端。      ----第一期代码的功能如下----      1.新用户注册      2.登陆并连接XMPP服务器 握手 保持在线 
http://www.itnose.net/detail/6264503.html 2015-05-06 10:18   [iphone]

Android仿博多图展示

NineGridLayout 1.简介 这是一个用于实现像朋友圈和博的类似的九宫格图片展示控件,通过自定义viewgroup实现,使用方便。 多图根据屏幕适配,单张图片时需要自己指定图片的宽; 2.使用方法 引用: compile 'com.w4lle.library
http://www.itnose.net/detail/6316793.html 2015-07-13 11:15   [Android]

android仿底部渐变导航栏

最近有很多人底部的变色卡片导航是怎么做的,我在网上看了好几个例子,都是效果接近,都存有一些差异,自己琢磨也做了一个,几乎99%的还原,效果还不错吧 仔细观察图片,发现他有两部分内容,外面的边框和里面的内容,内容的颜色由绿变为透明,这部分可以直接改变透明度,外面的边框,颜色在灰色和绿色之间变化,就不能简单的改变透明度了,ImageView的tint 为我们提供了可行方案,tint可以为图标着色,既可以在xml中,也可以在代码中设置,一共有16中模式,分别为 在xml中设置:直接添加tint属性,选择tintMode模式 <ImageView android:id="@+id/green" android:layout_width="50dp" android:layout_height="50dp" android:src="@mipmap/green" android:tint="@color/colorPrimary" android:tintMode="src_in"/> 在java代码中设置 mGreenImageView.setColorFilter(color,mode) mode参数类型 PorterDuff.Mode 为了理解不同颜色,不同透明度的图片在设置不同的tint模式,不同的颜色后的变化,写了一个demo,四个滑动条分别代表了颜色的alpha,R、G、B值,改变滑动为止,通过Color.argb(alpha,red,green,blue)动态组合出不同的颜色,通过Spinner选择不同的模式,通过给图像设置模式和不同的tint颜色,展示不同的效果,观察结果,可以知道给imageview设置tint后,图标最后展示出来的颜色不仅和设置的模式相关,还有图像的原有颜色和透明度相关。具体是怎样的相关性,语言描述不清,请自行体会。 选了实心绿,透明白,实心紫红的三张图片进行测试验证 界面很简单,只是一些基本的控件,代码如下 public class SimpleActivity extends Activity { private ImageView mGreenImageView; private ImageView mTransparentImageView; private ImageView mRedImageView; //透明度滑动条 private SeekBar mTransparentSeekBar; private Spinner mSpinner; //红色滑动条 private SeekBar mRedSeekBar; //绿色滑动条 private SeekBar mGreenSeekBar; //蓝色滑动条 private SeekBar mBlueSeekBar; private TextView mTextView; private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener; //滑动条监听器 //PorterDuff.Mode 列表 private static final PorterDuff.Mode[] MODES = new PorterDuff.Mode[]{ PorterDuff.Mode.ADD, PorterDuff.Mode.CLEAR, PorterDuff.Mode.DARKEN, PorterDuff.Mode.DST, PorterDuff.Mode.DST_ATOP, PorterDuff.Mode.DST_IN, PorterDuff.Mode.DST_OUT, PorterDuff.Mode.DST_OVER, PorterDuff.Mode.LIGHTEN, PorterDuff.Mode.MULTIPLY, PorterDuff.Mode.OVERLAY, PorterDuff.Mode.SCREEN, PorterDuff.Mode.SRC, PorterDuff.Mode.SRC_ATOP, PorterDuff.Mode.SRC_IN, PorterDuff.Mode.SRC_OUT, PorterDuff.Mode.SRC_OVER, PorterDuff.Mode.XOR }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_simple); mGreenImageView = (ImageView) findViewById(R.id.green); mTransparentImageView= (ImageView) findViewById(R.id.transparent); mRedImageView= (ImageView) findViewById(R.id.red); mTextView= (TextView) findViewById(R.id.text); mTransparentSeekBar = (SeekBar) findViewById(R.id.alpha_seekbar); mRedSeekBar= (SeekBar) findViewById(R.id.red_seekbar); mGreenSeekBar= (SeekBar) findViewById(R.id.green_seekbar); mBlueSeekBar= (SeekBar) findViewById(R.id.blue_seekbar); mSpinner= (Spinner) findViewById(R.id.spinner); SpinnerAdapter spinnerAdapter=ArrayAdapter.createFromResource(SimpleActivity.this,R.array.blend_modes, android.R.layout.simple_list_item_1); mSpinner.setAdapter(spinnerAdapter); initListener(); } private void initListener() { mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { updateImage(getRGBColor(),getMode()); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); mOnSeekBarChangeListener=new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { updateImage(getRGBColor(),getMode()); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }; mTransparentSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener); mRedSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener); mGreenSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener); mBlueSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener); } private PorterDuff.Mode getMode() { return MODES[mSpinner.getSelectedItemPosition()]; } /** * @return 根据ARGB颜色滑动条的数值计算颜色值 */ private int getRGBColor() { int alpha= mTransparentSeekBar.getProgress(); int red=mRedSeekBar.getProgress(); int green=mGreenSeekBar.getProgress(); int blue=mBlueSeekBar.getProgress(); return Color.argb(alpha,red,green,blue); } /** * 更新颜色 模式 * @param color * @param mode */ private void updateImage(int color, PorterDuff.Mode mode ) { mGreenImageView.setColorFilter(color,mode); mTransparentImageView.setColorFilter(color,mode); mRedImageView.setColorFilter(color,mode); mTextView.setTextColor(color); } }通过这个例子,可以看到在src_in模式下,如果给图标设置了tint,即着色后,图标显现的颜色和原有颜色无关,只和原来图像的透明度有关,图像越透明,着色越淡,图像实,着色越深 现在已经介绍了ImageView中tint使用的基础知识,下面我们就开始我们标题中所说的导航栏效果,我用肉眼能识别精度仔细观察了,发现主要有两个部分,外面的框和里面的内容,从上一页滑到下一页,滑动到一半前,上一页的外框保持不变,内容由绿色变为透明,下一页的外框由灰色变为绿色,滑到一半后,上一页的外框由绿色变为灰色,下一页的内容由透明变为绿色,现象已经描述的很清楚了,现在我们用两张图片实现效果,一张边框,一张内容,使用tint 的src_in模式,可以把任意纯色图片喜欢的颜色,为了方便,内容只改变透明度,选为绿色,边框任意,监听ViewPager滑动状态,根据ViewPager便宜量,计算当前颜色值,tint着色渲染 布局文件 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" tools:context="com.why.drawabletinttest.PagerActivity"> <android.support.v4.view.ViewPager android:layout_width="match_parent" android:id="@+id/pager_view" android:layout_height="match_parent"></android.support.v4.view.ViewPager> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom"> <LinearLayout android:layout_width="wrap_content" android
http://www.itnose.net/detail/6519000.html 2016-06-14 00:37   [Android]

仿朋友圈小菊花:weChatMoments

weChatMoments 仿朋友圈小菊花 废话不多说直接上图 项目地址: https://github.com/nacker/weChatMoments
http://www.itnose.net/detail/6428722.html 2016-01-03 10:50   [其它]

仿5.2.1主界面及消息提醒功能

;wrap_content" android:layout_marginLeft="12dp" android:text="" android:textColor="#d3d3d3" android:textSize="18sp"
http://www.itnose.net/detail/6218708.html 2015-03-06 10:12   [其它]

仿popupwindow

仿popupwindow 下载地址: http://www.devstore.cn/code/info/744.html 运行截图:
http://www.itnose.net/detail/6222106.html 2015-03-11 15:05   [其它]

仿

http://www.itnose.net/detail/6192900.html 2015-01-15 16:23   [其它]

仿

后台数据库设计 本菜鸟是快毕业的学生,最近在做一个类似的一款软件。要实现android的客户端和后台两个功能。 在后台服务端设计数据库时,感觉无从下手了.    开始设计了一张Userinfo表,字段是Id,name,pwd,imagePath
http://www.itnose.net/detail/754547.html 2014-02-20 16:11   [Android]

小程序开发日记--仿知乎日报(下)

本人对知乎日报是情有独钟,看我的博客和github就知道了,写了几个不同技术类型的知乎日报APP 要做小程序首先要对html,css,js有一定的基础,还有对小程序的API也要非常熟悉 我将该教程分为以下三篇 小程序日记——仿知乎日报(上) 小程序日记——仿知乎日报(中) 小程序日记——仿知乎日报(下) 三篇分别讲不同的组件和功能块 这篇要讲 主题日报 我的收藏 设置 图片修正 主题日报 主题日报的样式跟首页几乎一模一样,区别在多了一行主编区域。不过这个主编区域没有实现什么功能,本来是点击主编的头像跳转到主编的个人首页简介,没有时间安排就不做了,这也是需要解析html的(累),所以和主页共享一个页面根据主题id判断,如果是主题日报,顶部的swiper换成image,同时显示主编信息 主题日报列表需要接受一个具体的主题日报id,根据这个id来请求接口获取主题日报的日报列表。点击相应的主题触发这个方法,加载数据之后收起侧滑菜单 toThemePage: function( e ) { var _this = this; _this.setData( { loading: true,themeId:e.currentTarget.dataset.id }); console.log( 'themeId', _this.data.themeId ); requests.getThemeStories( _this.data.themeId, ( data ) => { data.background=data.background.replace("pic1","pic3"); data.background=data.background.replace("pic2","pic3"); for(var i=0;i<data.editors.length;i++){ data.editors[i].avatar=data.editors[i].avatar.replace("pic1","pic3"); data.editors[i].avatar=data.editors[i].avatar.replace("pic2","pic3"); } data=utils.correctData(data); _this.setData( { pageData: data.stories, background: data.background, description: data.description, editorData: data.editors }); slideDown.call( this ); }, null, () => { _this.setData( { loading: false }); }); }, 主题日报的请求列表方式和主页的列表方式差不多,由于没有发现分页参数,主题日报的日报列表这部分也没有分页请求。主题日报的日报详情还是跳转到日报详情页面的。 收藏页面 收藏页面就是只剩下下面列表项的,所以也是共用了主页的页面,在主题日报的基础上再判断,如果是我的收藏页面就不显示顶部图片和主编信息点击我的收藏触发以下方法 toCollectPage: function( ) { var _this = this; _this.setData( { themeId:-1}); var pageData = wx.getStorageSync('pageData') || [] console.log(pageData); _this.setData({ themeId:-1, pageData:pageData }) slideDown.call( this ); }, 设置页面 本来想做设置页面里列出的功能,但是工作比较忙,还是归入到后边的完善计划吧,现阶段只做了简单的页面布局。 但是还是讲一下自己的思路– 夜间模式就是改变应用的显示样式,利用到了css,我们可以在page中放置一个顶层的view来包括起所有的wxml元素,当切换主题时给页面顶层元素一个主题控制类。 <view class="light"> .... </view> <view class="night"> ... </view> 那怎么实现换肤立即生效呢?一个页面刚启动是会经过onLoad、onShow等,当第二次进来的时候页面的onLoad事件就不会在次触发,而是触发onShow事件,我们可以通过onShow事件来获取存在全局缓存中的主题设置。 onShow: function() { var app = getApp(); this.setData({theme: app.globalData.theme}); } <view class="{{theme}}"> ... </view> 清除缓存功能,当然是把临时文件和localStorage中的数据清空。 clearDataEvent: function() { wx.clearStorage(); //清除应用数据 } 应用的无图浏览模式跟主题的思路差不多,就是判断应用缓存中的设置是否是无图模式,如果是就在内容显示的时候加一个判断,根据这个值来判断是否显示图片类型的内容。 onLoad: function() { var app = getApp(); this.setData({imageMode: app.getImageMode()}); } <view> <image wx:if="{{imageMode}}" src="..." /> <!--或者--> <block wx:if="{{imageMode}}"> <image src="..." /> </block> </view> 图片修正 如果是用知乎日报提供的图片地址,如: http://pic1.zhimg.com//9e26ccbadca80e2e94f89d24b7ce6b04.jpg pic1和pic2的图片都不能正常显示,这应该是小程序开发工具的bug,所以我们只能把它们替换成pic3或者pic4,后来发现首页的列表和顶部图片用pic4可以正常显示,主编头像用pic3才能正常显示,详细页面也用pic3,我新建了一个方法来修正主页图片地址,其他那些只要在相应位置replace一下就好 /** * 修正图片url,将pic1和pic2改为pic4 * @param data * @returns {*} */ function correctData(data){ if (("top_stories" in data) ){ var top_stories=data.top_stories; for(var i = 0;i < top_stories.length; i++) { top_stories[i].image = top_stories[i].image.replace("pic1", "pic4"); top_stories[i].image = top_stories[i].image.replace("pic2", "pic4"); } data.top_stories=top_stories; } var stories=data.stories; for(var i = 0;i < stories.length; i++) { if (("images" in stories[i]) ){ var s=stories[i].images[0]; s=s.replace("pic1", "pic4"); s=s.replace("pic2", "pic4"); stories[i].images[0] =s; } } data.stories=stories; return data; } 总结 问题 代码结构比较烂,很多地方都没有优化处理,复用率较低,待重构。 页面布局有些不合理,尺寸控制的不够好。 部分wxml没有用模版功能代替重复的渲染工作,达不到复用效果。 闲语 本次编写的小程序用到了蛮多知识点,虽然花费了不少时间,但是一切都是非常的值得。编写的过程中遇到最大的困难就是解析html内容,可以说是绞尽脑汁,哈哈,智商不足啦。很期待能有网友能奉献出更好的解决方法。这个小例子做的比较简陋,很多功能没有完全实现,跟别人的Android和React仿客户端相比,小巫见大巫啦。还得抽空完成后续的更多功能。 到目前为止,小程序已经更新了几次,支持了ES5/ES6转换、下拉刷新事件、上传文件等功能,不过还有很多API还不能在模拟环境下显示效果。自己觉得一直做类似于豆瓣图书和知乎日报等除了网络请求之外没什么特别的地方的应用也不好,需要尝试新的API来扩展自己的视野,后续打算往未使用到的API进行案例制作。不知不觉已经踏出校园准备有4个月了,很怀念以前的学习日子,做过很多案例,但是都没有写日志和保存的习惯。这次写的字数蛮多的,可累死我了。很幸运自己初入工作圈就能碰上小程序风暴,期待它正式公测! 现阶段比较完整的效果动态图 本次示例的源码地址: http://www.hotlist.com.cn/archives/189.html 如果大家喜欢,给个start激励一下我,以后会有更好的作品与大家分享:)
http://www.itnose.net/detail/6707345.html 2017-01-12 18:46   [HTML5]

android仿表情输入与键盘输入详解

  最近公司在项目上要使用到表情与键盘的切换输入,自己实现了一个,还是存在些缺陷,比如说键盘与表情切换时出现跳闪问题,这个相当困扰我,不过所幸在Github(其中一个不错的开源项目,其代码整体结构很不错)并且在论坛上找些解决方案,再加上我也是研究了好多个开源项目的代码,最后才苦逼地整合出比较不错的实现效果,可以说跟基本一样(嘿嘿,只能说目前还没发现大Bug,若发现大家一起日后慢慢完善,这里我也只是给出了实现方案,拓展其他表情我并没有实现哈,不过代码中我实现了一个可拓展的fragment模板以便大家实现自己的表情包),我只是实现了一页表情,代码我也进行另外的封装与拓展,大家需要多表情的话只需要实现自己的表情fragment界面,然后根据工厂类获取即可,废话不多说先上图看效果: 效果还不错吧,哈哈。下面开始介绍: 本篇主要分析的核心类EmotionKeyboard.java,EmotionComplateFragment.java,EmotionMainFragment.java,FragmentFactory.java,还有一个是工具类里的EmotionUtils.java和GlobalOnItemClickManagerUtils.java 这几个类我会重点分析一下,其他的大家自行看源码哈。下面就开始咯,先来看看本篇主要内容以及大概思路: 1.解决表情与键盘切换跳闪问题 1.1跳闪问题概述   为了让大家对这个问题有一定了解,我先来个简单案例,用红色面板代表表情面板,效果如下:            图(1-1)   我们先来看图(1-1),即上图,通过上图我们可以看出,当表情显示时,我们点击表情按钮,隐藏表情显示软件盘时,内容Bar有一个明显的先向下后恢复的跳闪现象,这样用户体验相当的差,我们希望的是下图(1-2)的效果,无论怎么切换都不会有跳闪现象,这就是我所有说的键盘与表情切换的跳闪问题。            图(1-2) 到这里,我们对这个问题有了大概了解后,再来深入分析如何实现图(1-2)的不跳闪效果。这里我们做个约定,我们把含有表情那个bar统称为内容Bar。 1.2 解决跳闪问题的思路:   android系统在弹出软键盘时,会把我们的内容 Bar 顶上去,因此只有表情面板的高度与软键盘弹出时高度一致时,才有可能然切换时高度过渡更自然,所以我们必须计算出软键盘的高度并设置给表情面板。仅仅有这一步跳闪问题还是依旧存在,因此这时我们必须想其他办法固定内容Bar,因为所有的跳闪都是表情面板隐藏,而软键盘往上托出瞬间,Activity高度变(为什么会变后面会说明),内容Bar往下滑后,又被软键盘顶回原来位置造成的。因此只要固定了内容Bar的位置,闪跳问题就迎刃而解了。那么如何固定内容Bar的位置呢?我们知道在一个布局中一个控件的位置其实是由它上面所有控件的高度决定的,如果其上面其他控件的高度不变,那么当前控件的高度自然也不会变化,即使到时Activity的高度发生了变化也也不会影响该控件的位置(整个界面的显示是挂载在window窗体上的,而非Activity,不了解的可以先研究一下窗体的创建过程),因此我们只要在软键盘弹出前固定内容Bar上面所有控件高度,从而达到固定内容Bar位置(高度)的目的。好了,有思路了,我们接下来一步步按上面思路解决问题。 1.3 解决跳闪问题的套路: 1.3.1 先获取键盘高度,并设置表情面板的高度为软键盘的高度   Android系统在界面上弹出软键盘时会将整个Activity的高度压缩,此时windowSoftInputMode属性设置为adjustResize(对windowSoftInputMode不清楚的话,请自行查阅相关资料哈),这个属性表示Activity的主窗口总是会被调整大小,从而保证软键盘显示空间。在这种情况下我们可以通过以下方法计算软键盘的高度: Rect r = new Rect(); /* * decorView是window中的最顶层view,可以从window中通过getDecorView获取到decorView。 * 通过decorView获取到程序显示的区域,包括标题栏,但不包括状态栏。 */ mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r); //获取屏幕的高度 int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight(); //计算软件盘的高度 int softInputHeight = screenHeight - r.bottom; 这里我们队对r.bottom和mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r)进行简单解释,直接上图吧: 这下就清晰了吧,右边是Rect参数解析图,辅助我们对rect的理解。 Rect r = new Rect(); mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r)   这两句其实将左图中蓝色边框( 其实也就是actvity的大小)的size大小参数封装到Rect中,以便我们后续使用。虽然计算出来的区域大小不包含状态栏,但是r.bottom(红色箭头长度)的大小是从屏幕顶部开始计算的所以包含了状态栏的高度。需要注意的是,区域大小是这样计算出来的: 区域的:r.bottom-r.top 区域的宽:r.right-r.left 当然这个跟计算软键盘高度没关系,只是顺带提一下。因此我们可以通过即可获取到软以下方式获取键盘高度: 键盘高度=屏幕高度-r.bottom 1.3.2 固定内容Bar的高度,解决闪跳问题   软键盘高度解决后,现在剩下的问题关键就在于控制内容Bar的高度了,那么如何做呢?我们先来看一个布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/listview" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="0dp" /> <FrameLayout android:id="@+id/fl_emotionview_main" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>   其中ListView的layout_height为0dp、layout_weight为1,这样这个ListView就会自动充满整个布局,这里ListView可以替换成任意控件,FrameLayout则为表情布局(也可认为就是我们前面所说的内容Bar,只不过这里最终会被替换成整个表情布局),我们的目的就是在弹出软键盘时固定FrameLayout的高度,以便去除跳闪问题。根据我们前面的思路,FrameLayout的高度是由其上面的控件决定的也就是由ListView决定的,也就是说我们只要在软键盘弹出前固定ListView的内容高度即可。因此我们可以通过下面的方法来锁定ListView的高度,(mContentView就是我们所指的ListView,这些方法都封装在EmotionKeyboard.java类中) /** * 锁定内容高度,防止跳闪 */ private void lockContentHeight(){ LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams(); params.height = mContentView.getHeight(); params.weight = 0.0F; }   将weight置0,然后将height设置为当前的height,在父控件(LinearLayout)的高度变化时它的高度也不再会变化。释放ListView的高度: private void unlockContentHeightDelayed() { mEditText.postDelayed(new Runnable() { @Override public void run() { ((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F; } }, 200L); }   其中的LinearLayout.LayoutParams.weight = 1.0F;,在代码里动态更改LayoutParam的weight,会导致父控件重新onLayout(),也就达到改变控件的高度的目的。到此两个主要问题都解决了,我们直接上核心类代码,该类来自github上的开源项目我在使用中直接从该项目中抽取了该类, 并做了细微修改,也添加了代码注释。 package com.zejian.emotionkeyboard.emotionkeyboardview; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Rect; import android.os.Build; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.LinearLayout; import com.zejian.emotionkeyboard.utils.LogUtils; /** * author : zejian * time : 2016年1月5日 上午11:14:27 * email : shinezejian@163.com * description :源码来自开源项目https://github.com/dss886/Android-EmotionInputDetector * 本人仅做细微修改以及代码解析 */ public class EmotionKeyboard { private static final String SHARE_PREFERENCE_NAME = "EmotionKeyboard"; private static final String SHARE_PREFERENCE_SOFT_INPUT_HEIGHT = "soft_input_height"; private Activity mActivity; private InputMethodManager mInputManager;//软键盘管理类 private SharedPreferences sp; private View mEmotionLayout;//表情布局 private EditText mEditText;// private View mContentView;//内容布局view,即除了表情布局或者软键盘布局以外的布局,用于固定bar的高度,防止跳闪 private EmotionKeyboard(){ } /** * 外部静态调用 * @param activity * @return */ public static EmotionKeyboard with(Activity activity) { EmotionKeyboard emotionInputDetector = new EmotionKeyboard(); emotionInputDetector.mActivity = activity; emotionInputDetector.mInputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); emotionInputDetector.sp = activity.getSharedPreferences(SHARE_PREFERENCE_NAME, Context.MODE_PRIVATE); return emotionInputDetector; } /** * 绑定内容view,此view用于固定bar的高度,防止跳闪 * @param contentView * @return */ public EmotionKeyboard bindToContent(View contentView) { mContentView = contentView; return this; } /** * 绑定编辑框 * @param editText * @return */ public EmotionKeyboard bindToEditText(EditText editText) { mEditText = editText; mEditText.requestFocus(); mEditText.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP && mEmotionLayout.isShown()) { lockContentHeight();//显示软件盘时,锁定内容高度,防止跳闪。 hideEmotionLayout(true);//隐藏表情布局,显示软件盘 //软件盘显示后,释放内容高度 mEditText.postDelayed(new Runnable() { @Override public void run() { unlockContentHeightDelayed(); } }, 200L); } return false; } }); return this; } /** * 绑定表情按钮 * @param emotionButton * @return */ public EmotionKeyboard bindToEmotionButton(View emotionButton) { emotionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mEmotionLayout.isShown()) { lockContentHeight();//显示软件盘时,锁定内容高度,防止跳闪。 hideEmotionLayout(true);//隐藏表情布局,显示软件盘 unlockContentHeightDelayed();//软件盘显示后,释放内容高度 } else { if (isSoftInputShown()) {//同上 lockContentHeight(); showEmotionLayout(); unlockContentHeightDelayed(); } else { showEmotionLayout();//两者都没显示,直接显示表情布局 } } } }); return this; } /** * 设置表情内容布局 * @param emotionView * @return */ public EmotionKeyboard setEmotionView(View emotionView) { mEmotionLayout = emotionView; return this; } public EmotionKeyboard build(){ //设置软件盘的模式:SOFT_INPUT_ADJUST_RESIZE 这个属性表示Activity的主窗口总是会被调整大小,从而保证软键盘显示空间。 //从而方便我们计算软件盘的高度 mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); //隐藏软件盘 hideSoftInput(); return this; } /** * 点击返回键时先隐藏表情布局 * @return */ public boolean interceptBackPress() { if (mEmotionLayout.isShown()) { hideEmotionLayout(false); return true; } return false; } private void showEmotionLayout() { int softInputHeight = getSupportSoftInputHeight(); if (softInputHeight == 0) { softInputHeight = sp.getInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, 400); } hideSoftInput(); mEmotionLayout.getLayoutParams().height = softInputHeight; mEmotionLayout.setVisibility(View.VISIBLE); } /** * 隐藏表情布局 * @param showSoftInput 是否显示软件盘 */ private void hideEmotionLayout(boolean showSoftInput) { if (mEmotionLayout.isShown()) { mEmotionLayout.setVisibility(View.GONE); if (showSoftInput) { showSoftInput(); } } } /** * 锁定内容高度,防止跳闪 */ private void lockContentHeight() { LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams(); params.height = mContentView.getHeight(); params.weight = 0.0F; } /** * 释放被锁定的内容高度 */ private void unlockContentHeightDelayed() { mEditText.postDelayed(new Runnable() { @Override public void run() { ((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F; } }, 200L); } /** * 编辑框获取焦点,并显示软件盘 */ private void showSoftInput() { mEditText.requestFocus(); mEditText.post(new Runnable() { @Override public void run() { mInputManager.showSoftInput(mEditText, 0); } }); } /** * 隐藏软件盘 */ private void hideSoftInput() { mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); } /** * 是否显示软件盘 * @return */ private boolean isSoftInputShown() { return getSupportSoftInputHeight() != 0; } /** * 获取软件盘的高度 * @return */ private int getSupportSoftInputHeight() { Rect r = new Rect(); /** * decorView是window中的最顶层view,可以从window中通过getDecorView获取到decorView。 * 通过decorView获取到程序显示的区域,包括标题栏,但不包括状态栏。 */ mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r); //获取屏幕的高度 int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight(); //计算软件盘的高度 int softInputHeight = screenHeight - r.bottom; /** * 某些Android版本下,没有显示软键盘时减出来的高度总是144,而不是零, * 这是因为高度是包括了虚拟按键栏的(例如华为系列),所以在API Level高于20时, * 我们需要减去底部虚拟按键栏的高度(如果有的话) */ if (Build.VERSION.SDK_INT >= 20) { // When SDK Level >= 20 (Android L), the softInputHeight will contain the height of softButtonsBar (if has) softInputHeight = softInputHeight - getSoftButtonsBarHeight(); } if (softInputHeight < 0) { LogUtils.w("EmotionKeyboard--Warning: value of softInputHeight is below zero!"); } //存一份到本地 if (softInputHeight > 0) { sp.edit().putInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, softInputHeight).apply(); } return softInputHeight; } /** * 底部虚拟按键栏的高度 * @return */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private int getSoftButtonsBarHeight() { DisplayMetrics metrics = new DisplayMetrics(); //这个方法获取可能不是真实屏幕的高度 mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics); int usableHeight = metrics.heightPixels; //获取当前屏幕的真实高度 mActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics); int realHeight = metrics.heightPixels; if (realHeight > usableHeight) { return realHeight - usableHeight; } else { return 0; } } /** * 获取软键盘高度 * @return */ public int getKeyBoardHeight(){ return sp.getInt(SHARE_PREFERENCE_SOFT_INPUT_HEIGHT, 400); } }   EmotionKeyboard类使用的是设计模式中的builder模式来创建对象。其中mEmotionLayout是表情布局,mContentView是内容布局view,即除了表情布局或者软键盘布局以外的布局,用于固定bar的高度,防止跳闪,当然mContentView可以是任意布局。 /** * 绑定表情按钮 * @param emotionButton * @return */ public EmotionKeyboard bindToEmotionButton(View emotionButton) { emotionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mEmotionLayout.isShown()) { lockContentHeight();//显示软件盘时,锁定内容高度,防止跳闪。 hideEmotionLayout(true);//隐藏表情布局,显示软件盘 unlockContentHeightDelayed();//软件盘显示后,释放内容高度 } else { if (isSoftInputShown()) {//同上 lockContentHeight(); showEmotionLayout(); unlockContentHeightDelayed(); } else { showEmotionLayout();//两者都没显示,直接显示表情布局 } } } }); return this; }   这里我们主要重点说明一下点击表情按钮时,显示或者隐藏表情布局以及软键盘的逻辑。首先我们通过mEmotionLayout.isShown()去判断表情是否已经显示,如果返回true,这时肯定要去切换成软键盘,因此必须先通过lockContentHeight()方法锁定mContentView内容高度,然后通过hideEmotionLayout(true)方法因此表情布局并显示软键盘,这里传入true表示显示软键盘,如果传入false则表示不显示软键盘,软键盘显示后通过unlockContentHeightDelayed()方法去解锁mContentView内容高度。但如果mEmotionLayout.isShown()返回了false,这有两种情况,第1种是如果此时软键盘已经显示,则需先锁定mContentView内容高度,再去隐藏软键盘,然后显示表情布局,最后再解锁mContentView内容高度。第2种情况是软键盘和表情都没显示,这下就简单了,直接显示表情布局即可。好,这个类解析到这,其他直接看源码哈,注释杠杠的哈。最后我们来看看在外部使用该类的例子代码如下: mEmotionKeyboard = EmotionKeyboard.with(getActivity()) .setEmotionView(rootView.findViewById(R.id.ll_emotion_layout))//绑定表情面板 .bindToContent(contentView)//绑定内容view .bindToEditText(!isBindToBarEditText ? ((EditText) contentView) : ((EditText) rootView.findViewById(R.id.bar_edit_text)))//判断绑定那种EditView .bindToEmotionButton(rootView.findViewById(R.id.emotion_button))//绑定表情按钮 .build(); 2.实现表情表情面板切换的思路   这里我们主要采用NoHorizontalScrollerViewPager+RecyclerView+Fragment实现,思路是这样的,我们以NoHorizontalScrollerViewPager作为载体,fragment作为展示界面,RecyclerView作为底部滚动条,每当点击RecyclerView的item时,我们使用viewPager.setCurrentItem(position,false)方法来切换fragment界面即可(这里传入false是表示不需要viewPager的切换动画)。这样我们就可以实现不同类表情的切换了。(提示一下这里所指的fragment其实是就工程目录中的EmotiomComplateFragment.java类)这个比较简单,就不多啰嗦了。实现代码稍后会一起提供。下面是不可横向滑动的ViewPager的实现代码,非常简单,不拦截子类事件即可。 package com.zejian.emotionkeyboard.emotionkeyboardview; import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; /** * Created by zejian * Time 16/1/7 上午11:12 * Email shinezejian@163.com * Description:不可横向滑动的ViewPager */ public class NoHorizontalScrollerViewPager extends ViewPager{ public NoHorizontalScrollerViewPager(Context context) { super(context); } public NoHorizontalScrollerViewPager(Context context, AttributeSet attrs) { super(context, attrs); } /** * 重写拦截事件,返回值设置为false,这时便不会横向滑动了。 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } /** * 重写拦截事件,返回值设置为false,这时便不会横向滑动了。 * @param ev * @return */ @Override public boolean onTouchEvent(MotionEvent ev) { return false; } } 3.单个表情面板的实现思路 3.1 表情图片的本质与显示   表情的显示从直观上看确实是一个图片,但实际只是一种特殊的文本(ImageSpan),比如博里表情就是”[表情名字]”的接口,可爱的表情就是[可爱]…因此这里我们也打算利用”[表情名字]”作为key,图片的R值作为内容进行存取,EmotionUtils类如下 package com.zejian.emotionkeyboard.utils; import android.support.v4.util.ArrayMap; import com.zejian.emotionkeyboard.R; /** * @author : zejian * @time : 2016年1月5日 上午11:32:33 * @email : shinezejian@163.com * @description :表情加载类,可自己添加多种表情,分别建立不同的map存放和不同的标志符即可 */ public class EmotionUtils { /** * 表情类型标志符 */ public static final int EMOTION_CLASSIC_TYPE=0x0001;//经典表情 /** * key-表情文字; * value-表情图片资源 */ public static ArrayMap<String, Integer> EMPTY_MAP; public static ArrayMap<String, Integer> EMOTION_CLASSIC_MAP; static { EMPTY_MAP = new ArrayMap<>(); EMOTION_CLASSIC_MAP = new ArrayMap<>(); EMOTION_CLASSIC_MAP.put("[呵呵]", R.drawable.d_hehe); EMOTION_CLASSIC_MAP.put("[嘻嘻]", R.drawable.d_xixi); EMOTION_CLASSIC_MAP.put("[哈哈]", R.drawable.d_haha); EMOTION_CLASSIC_MAP.put("[爱你]", R.drawable.d_aini); EMOTION_CLASSIC_MAP.put("[挖鼻屎]", R.drawable.d_wabishi); EMOTION_CLASSIC_MAP.put("[吃惊]", R.drawable.d_chijing); EMOTION_CLASSIC_MAP.put("[晕]", R.drawable.d_yun); EMOTION_CLASSIC_MAP.put("[泪]", R.drawable.d_lei); EMOTION_CLASSIC_MAP.put("[馋嘴]", R.drawable.d_chanzui); EMOTION_CLASSIC_MAP.put("[抓狂]", R.drawable.d_zhuakuang); EMOTION_CLASSIC_MAP.put("[哼]", R.drawable.d_heng); EMOTION_CLASSIC_MAP.put("[可爱]", R.drawable.d_keai); EMOTION_CLASSIC_MAP.put("[怒]", R.drawable.d_nu); EMOTION_CLASSIC_MAP.put("[汗]", R.drawable.d_han); EMOTION_CLASSIC_MAP.put("[害羞]", R.drawable.d_haixiu); EMOTION_CLASSIC_MAP.put("[睡觉]", R.drawable.d_shuijiao); EMOTION_CLASSIC_MAP.put("[钱]", R.drawable.d_qian); EMOTION_CLASSIC_MAP.put("[偷笑]", R.drawable.d_touxiao); EMOTION_CLASSIC_MAP.put("[笑cry]", R.drawable.d_xiaoku); EMOTION_CLASSIC_MAP.put("[doge]", R.drawable.d_doge); EMOTION_CLASSIC_MAP.put("[喵喵]", R.drawable.d_miao); EMOTION_CLASSIC_MAP.put("[酷]", R.drawable.d_ku); EMOTION_CLASSIC_MAP.put("[衰]", R.drawable.d_shuai); EMOTION_CLASSIC_MAP.put("[闭嘴]", R.drawable.d_bizui); EMOTION_CLASSIC_MAP.put("[鄙视]", R.drawable.d_bishi); EMOTION_CLASSIC_MAP.put("[花心]", R.drawable.d_huaxin); EMOTION_CLASSIC_MAP.put("[鼓掌]", R.drawable.d_guzhang); EMOTION_CLASSIC_MAP.put("[悲伤]", R.drawable.d_beishang); EMOTION_CLASSIC_MAP.put("[思考]", R.drawable.d_sikao); EMOTION_CLASSIC_MAP.put("[生病]", R.drawable.d_shengbing); EMOTION_CLASSIC_MAP.put("[亲亲]", R.drawable.d_qinqin); EMOTION_CLASSIC_MAP.put("[怒骂]", R.drawable.d_numa); EMOTION_CLASSIC_MAP.put("[太开心]", R.drawable.d_taikaixin); EMOTION_CLASSIC_MAP.put("[懒得理你]", R.drawable.d_landelini); EMOTION_CLASSIC_MAP.put("[右哼哼]", R.drawable.d_youhengheng); EMOTION_CLASSIC_MAP.put("[左哼哼]", R.drawable.d_zuohengheng); EMOTION_CLASSIC_MAP.put("[嘘]", R.drawable.d_xu); EMOTION_CLASSIC_MAP.put("[委屈]", R.drawable.d_weiqu); EMOTION_CLASSIC_MAP.put("[吐]", R.drawable.d_tu); EMOTION_CLASSIC_MAP.put("[可怜]", R.drawable.d_kelian); EMOTION_CLASSIC_MAP.put("[打哈气]", R.drawable.d_dahaqi); EMOTION_CLASSIC_MAP.put("[挤眼]", R.drawable.d_jiyan); EMOTION_CLASSIC_MAP.put("[失望]", R.drawable.d_shiwang); EMOTION_CLASSIC_MAP.put("[顶]", R.drawable.d_ding); EMOTION_CLASSIC_MAP.put("[疑问]", R.drawable.d_yiwen); EMOTION_CLASSIC_MAP.put("[困]", R.drawable.d_kun); EMOTION_CLASSIC_MAP.put("[感冒]", R.drawable.d_ganmao); EMOTION_CLASSIC_MAP.put("[拜拜]", R.drawable.d_baibai); EMOTION_CLASSIC_MAP.put("[黑线]", R.drawable.d_heixian); EMOTION_CLASSIC_MAP.put("[阴险]", R.drawable.d_yinxian); EMOTION_CLASSIC_MAP.put("[打脸]", R.drawable.d_dalian); EMOTION_CLASSIC_MAP.put("[傻眼]", R.drawable.d_shayan); EMOTION_CLASSIC_MAP.put("[猪头]", R.drawable.d_zhutou); EMOTION_CLASSIC_MAP.put("[熊猫]", R.drawable.d_xiongmao); EMOTION_CLASSIC_MAP.put("[兔子]", R.drawable.d_tuzi); } /** * 根据名称获取当前表情图标R值 * @param EmotionType 表情类型标志符 * @param imgName 名称 * @return */ public static int getImgByName(int EmotionType,String imgName) { Integer integer=null; switch (EmotionType){ case EMOTION_CLASSIC_TYPE: integer = EMOTION_CLASSIC_MAP.get(imgName); break; default: LogUtils.e("the emojiMap is null!!"); break; } return integer == null ? -1 : integer; } /** * 根据类型获取表情数据 * @param EmotionType * @return */ public static ArrayMap<String, Integer> getEmojiMap(int EmotionType){ ArrayMap EmojiMap=null; switch (EmotionType){ case EMOTION_CLASSIC_TYPE: EmojiMap=EMOTION_CLASSIC_MAP; break; default: EmojiMap=EMPTY_MAP; break; } return EmojiMap; } }   ArrayMap /** * 获取fragment的方法 * @param emotionType 表情类型,用于判断使用哪个map集合的表情 */ public Fragment getFragment(int emotionType){ Bundle bundle = new Bundle(); bundle.putInt(FragmentFactory.EMOTION_MAP_TYPE,emotionType); EmotiomComplateFragment fragment= EmotiomComplateFragment.newInstance(EmotiomComplateFragment.class,bundle); return fragment; } 调用时,如下: //创建fragment的工厂类 FragmentFactory factory=FragmentFactory.getSingleFactoryInstance(); //创建修改实例 EmotiomComplateFragment f1= (EmotiomComplateFragment) factory.getFragment(EmotionUtils.EMOTION_CLASSIC_TYPE);   这里我们通过工厂类getFragment(int emotionType)方法的创建出模版表情类EmotiomComplateFragment,为什么说是模版呢,因为只要我们创建时传递集合标志不同,例如经典表情传递的就是EmotionUtils.EMOTION_CLASSIC_TYPE,这时EmotiomComplateFragment类内部就会根据传递的集合类型去EmotionUtils类中获取相对应的集合,这样也就会创建出我们所需要的表情面板。这里小结一下:通过上术分析我们可以知道如果我们要添加自己的其他类型表情,只需以下步骤: 步骤1.在EmotionUtils类创建一个表情集合,并赋予这个集合唯一标志 步骤2.在EmotionUtils类中的两个获取方法中完善相应的代码。 步骤3.在创建新的EmotiomComplateFragment模板类时,传递相应的集合标志符即可创建相应的表情面板。   接下来的问题就是表情如何显示呢?其实这里主要用到了SpannableString拓展性字符串相关知识点,SpannableString可以让一段字符串在显示的时候,将其中某小段文字附着上其他内容或替换成其他内容,拓展内容可以是图片或者是文字格式,比如加粗,显示特殊颜色等。对于SpannableString不熟悉,可先看看这篇文章 ,下面我只对本篇需要用到的SpannableString作简要介绍:   ImageSpan,这个是可以将指定的特殊字符替换成我们所需要的图片。也就是我们可以使用”[表情名字]”这个key作为指定的特殊字符,然后在文本中替换成该key所对应的特殊表情即可。 简单实例如下: SpannableString spannableString = new SpannableString(source); int size = (int) tv.getTextSize()*13/10; Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes); Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true); ImageSpan span = new ImageSpan(context, scaleBitmap); spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);   首先将我们要替换的字符串转换成SpannableString再创建一个ImageSpan并把我们的表情图片包含在内,最后利用SpannableString的setSpan方法,将span对象设置在对应位置,这样就完成了特殊字符与文字的转换。参数解析如下, start 是需要附着的内容的开始位置 end 是需要附着的内容的开始位置 flag 标志位,这里是最常用的EXCLUSIVE_EXCLUSIVE的表示span拓展文本不包含前后(这个参数还有其他类型,这里不过多介绍) 3.2 利用正则表达式找出特殊字符便于转换成表情   这里我们利用正则表达式找出特殊字符,根据我们自己的需求编写特定的正则表达式,如下: String regex = "\\[[\u4e00-\u9fa5\\w]+\\]"; 其中[]是我们特殊需要的字符,因此必须使用“//”进行转义,\u4e00-\u9fa5表示中文,\w表示下划线的任意单词字符,+ 代表一个或者多个。因此这段正则就代表,匹配方括号内有一或多个文字和单词字符的文本。有了正则表达式,剩下就是找匹配的问题了,这里我们可以先用matcher.find()获取到匹配的开始位置,作为setSpan的start值,再使用matcher.group()方法获取到匹配规则的具体表情文字。对于matcher.find()和matcher.group()这里简单介绍一下: matcher.find(),代表部分匹配,从当前位置开始匹配,找到一个匹配的子串,将移动下次匹配的位置。因此我们可以通过这个方法获取到匹配的开始位置,作为setSpan的start值(如果字符串中有多个表情就会执行多次匹配)。 matcher.group(),获取匹配到的具体字符。 下面直接上SpanStringUtils.java类对代码: package com.zejian.emotionkeyboard.utils; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ImageSpan; import android.widget.TextView; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author : zejian * @time : 2016年1月5日 上午11:30:39 * @email : shinezejian@163.com * @description :文本中的emojb字符处理为表情图片 */ public class SpanStringUtils { public static SpannableString getEmotionContent(int emotion_map_type,final Context context, final TextView tv, String source) { SpannableString spannableString = new SpannableString(source); Resources res = context.getResources(); String regexEmotion = "\\[([\u4e00-\u9fa5\\w])+\\]"; Pattern patternEmotion = Pattern.compile(regexEmotion); Matcher matcherEmotion = patternEmotion.matcher(spannableString); while (matcherEmotion.find()) { // 获取匹配到的具体字符 String key = matcherEmotion.group(); // 匹配字符串的开始位置 int start = matcherEmotion.start(); // 利用表情名字获取到对应的图片 Integer imgRes = EmotionUtils.getImgByName(emotion_map_type,key); if (imgRes != null) { // 压缩表情图片 int size = (int) tv.getTextSize()*13/10; Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes); Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true); ImageSpan span = new ImageSpan(context, scaleBitmap); spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } return spannableString; } } 代码相对比较简单,这里就不啰嗦啦。 3.3 表情面板的实现(ViewPager+GridView)   这里的自然就是使用到ViewPager和GridView相结合实现多界面滑动的效果,参考了的实现,每页都是一个GridView显示20个表情,末尾还有一个删除按钮。实现思路入下:   利用ViewPager作为滑动控件,同时结合GridView来布局每个表情,GridView会显示3行7列,共21个Item,即每页都是一个GridView显示20个表情,末尾还有一个删除按钮。为了让Item能大小合适,我们在这里利用动态计算的方式设置宽,因为屏幕宽度各有不同。每个item宽度的计算方式,由(屏幕的宽度-左右边距大小(如果有的话就减去)-每个item间隙距离)/7,最终便得到item的宽度。至于表情面板的高度=(item宽度*3+间隙*6),即可获取中高度,为什么间隙*6?这里并没有什么计算原理,纯粹是我在调试的过程中试出来的值,这个值相对比较合理,也比较美观,当然大家也可根据自己需要调整。最后就是有多少页的问题了,这里可以通过for循环表情集合的所有元素,把每次循环获取的元素添加到一个集合中,每次判断集合是否满20个元素,每满20个集合就利用该集合去创建一个GridView的表情面板View,同时再新建一个集合存放新获取到的元素,以次循环。最后把所有表情生成的一个个GridView放到一个总view集合中,利用ViewPager显示即可。要注意的是在GridView的适配器和点击事件中,都利用position判断,如果是最后一个就进行特殊的显示(删除按钮)和点击处理。 package com.zejian.emotionkeyboard.fragment; import android.os.Bundle; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.GridView; import android.widget.LinearLayout; import com.zejian.emotionkeyboard.R; import com.zejian.emotionkeyboard.adapter.EmotionGridViewAdapter; import com.zejian.emotionkeyboard.adapter.EmotionPagerAdapter; import com.zejian.emotionkeyboard.emotionkeyboardview.EmojiIndicatorView; import com.zejian.emotionkeyboard.utils.DisplayUtils; import com.zejian.emotionkeyboard.utils.EmotionUtils; import com.zejian.emotionkeyboard.utils.GlobalOnItemClickManagerUtils; import java.util.ArrayList; import java.util.List; /** * Created by zejian * Time 16/1/5 下午4:32 * Email shinezejian@163.com * Description:可替换的模板表情,gridview实现 */ public class EmotiomComplateFragment extends BaseFragment { private EmotionPagerAdapter emotionPagerGvAdapter; private ViewPager vp_complate_emotion_layout; private EmojiIndicatorView ll_point_group;//表情面板对应的点列表 private int emotion_map_type; /** * 创建与Fragment对象关联的View视图时调用 * @param inflater * @param container * @param savedInstanceState * @return */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_complate_emotion, container, false); initView(rootView); initListener(); return rootView; } /** * 初始化view控件 */ protected void initView(View rootView){ vp_complate_emotion_layout = (ViewPager) rootView.findViewById(R.id.vp_complate_emotion_layout); ll_point_group= (EmojiIndicatorView) rootView.findViewById(R.id.ll_point_group); //获取map的类型 emotion_map_type=args.getInt(FragmentFactory.EMOTION_MAP_TYPE); initEmotion(); } /** * 初始化监听器 */ protected void initListener(){ vp_complate_emotion_layout.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { int oldPagerPos=0; @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { ll_point_group.playByStartPointToNext(oldPagerPos,position); oldPagerPos=position; } @Override public void onPageScrollStateChanged(int state) { } }); } /** * 初始化表情面板 * 思路:获取表情的总数,按每行存放7个表情,动态计算出每个表情所占的宽度大小(包含间距), * 而每个表情的与宽应该是相等的,这里我们约定只存放3行 * 每个面板最多存放7*3=21个表情,再减去一个删除键,即每个面板包含20个表情 * 根据表情总数,循环创建多个容量为20的List,存放表情,对于大小不满20进行特殊 * 处理即可。 */ private void initEmotion() { // 获取屏幕宽度 int screenWidth = DisplayUtils.getScreenWidthPixels(getActivity()); // item的间距 int spacing = DisplayUtils.dp2px(getActivity(), 12); // 动态计算item的宽度和高度 int itemWidth = (screenWidth - spacing * 8) / 7; //动态计算gridview的总高度 int gvHeight = itemWidth * 3 + spacing * 6; List<GridView> emotionViews = new ArrayList<>(); List<String> emotionNames = new ArrayList<>(); // 遍历所有的表情的key for (String emojiName : EmotionUtils.getEmojiMap(emotion_map_type).keySet()) { emotionNames.add(emojiName); // 每20个表情作为一组,同时添加到ViewPager对应的view集合中 if (emotionNames.size() == 20) { GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight); emotionViews.add(gv); // 添加完一组表情,重新创建一个表情名字集合 emotionNames = new ArrayList<>(); } } // 判断最后是否有不足20个表情的剩余情况 if (emotionNames.size() > 0) { GridView gv = createEmotionGridView(emotionNames, screenWidth, spacing, itemWidth, gvHeight); emotionViews.add(gv); } //初始化指示器 ll_point_group.initIndicator(emotionViews.size()); // 将多个GridView添加显示到ViewPager中 emotionPagerGvAdapter = new EmotionPagerAdapter(emotionViews); vp_complate_emotion_layout.setAdapter(emotionPagerGvAdapter); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(screenWidth, gvHeight); vp_complate_emotion_layout.setLayoutParams(params); } /** * 创建显示表情的GridView */ private GridView createEmotionGridView(List<String> emotionNames, int gvWidth, int padding, int itemWidth, int gvHeight) { // 创建GridView GridView gv = new GridView(getActivity()); //设置点击背景透明 gv.setSelector(android.R.color.transparent); //设置7列 gv.setNumColumns(7); gv.setPadding(padding, padding, padding, padding); gv.setHorizontalSpacing(padding); gv.setVerticalSpacing(padding * 2); //设置GridView的宽 ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(gvWidth, gvHeight); gv.setLayoutParams(params); // 给GridView设置表情图片 EmotionGridViewAdapter adapter = new EmotionGridViewAdapter(getActivity(), emotionNames, itemWidth,emotion_map_type); gv.setAdapter(adapter); //设置全局点击事件 gv.setOnItemClickListener(GlobalOnItemClickManagerUtils.getInstance(getActivity()).getOnItemClickListener(emotion_map_type)); return gv; } } 注释非常清晰哈。我就不啰嗦了。但这有个要注意的是在for循环时是通过EmotionUtils的getEmojiMap(emotion_map_type).keySet()获取集合,这也印证前面我们所说的EmotiomComplateFragment内部是通过集合标志判断集合类型,最终获取到所需的集合数据,也就生成了不同表情类型的面板。 3.4 表情的输入框插入和删除   思路:在表情框输入一个表情实际上是在当前光标位置插入一个表情,添加完表情后再把当前光标移动到表情之后,所以我们首先要获取到光标到首位置,这个可以利用EditText.setSelectionStart()方法,添加完表情后要设置光标的位置到表情之后,这个可以使用EditText.setSelection(position)方法。当然如果点击的是删除按钮,那么直接调用系统的 Delete 按钮事件即可。下面直接上代码: // 点击的是表情 EmotionGridViewAdapter emotionGvAdapter = (EmotionGridViewAdapter) itemAdapter; if (position == emotionGvAdapter.getCount() - 1) { // 如果点击了最后一个回退按钮,则调用删除键事件 mEditText.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); } else { // 如果点击了表情,则添加到输入框中 String emotionName = emotionGvAdapter.getItem(position); // 获取当前光标位置,在指定位置上添加表情图片文本 int curPosition = mEditText.getSelectionStart(); StringBuilder sb = new StringBuilder(mEditText.getText().toString()); sb.insert(curPosition, emotionName); // 特殊文字处理,将表情等转换一下 mEditText.setText(SpanStringUtils.getEmotionContent(emotion_map_type, mContext, mEditText, sb.toString())); // 将光标设置到新增完表情的右侧 mEditText.setSelection(curPosition + emotionName.length()); }   这里要理解一点就是让控件调用系统事件的方法为EditText.displatchKeyEvent(new KeyEvent(action, code));其中action就是动作,用ACTION_DOWN按下动作就可以了而 code为按钮事件码,删除对应的就是KEYCODE_DEL。 4.表情点击事件全局监听的实现   上面弄明白了表情的输入与删除操作后,我们就要考虑一个问题了,那就是在哪里设置监听?直接在创建GridView时,这个确实行得通,不过我们还要再考虑一个问题,那就是如果我们存在多个GridView呢?多复制几遍咯。但我们是高级工程师对吧,这样重复代码显然是不可出现在我们眼前的,因此这里我们决定使用全局监听来设置点击事件,当然这个并非我想到的,这个是在github开源项目我在阅读源码时,发现的,这种方式挺不错,我就拿来用咯。直接上代码: package com.zejian.emotionkeyboard.utils; import android.content.Context; import android.view.KeyEvent; import android.view.View; import android.widget.AdapterView; import android.widget.EditText; import com.zejian.emotionkeyboard.adapter.EmotionGridViewAdapter; /** * Created by zejian * Time 16/1/8 下午5:05 * Email shinezejian@163.com * Description:点击表情的全局监听管理类 */ public class GlobalOnItemClickManagerUtils { private static GlobalOnItemClickManagerUtils instance; private EditText mEditText;//输入框 private static Context mContext; public static GlobalOnItemClickManagerUtils getInstance(Context context) { mContext=context; if (instance == null) { synchronized (GlobalOnItemClickManagerUtils.class) { if(instance == null) { instance = new GlobalOnItemClickManagerUtils(); } } } return instance; } public void attachToEditText(EditText editText) { mEditText = editText; } public AdapterView.OnItemClickListener getOnItemClickListener(final int emotion_map_type) { return new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Object itemAdapter = parent.getAdapter(); if (itemAdapter instanceof EmotionGridViewAdapter) { // 点击的是表情 EmotionGridViewAdapter emotionGvAdapter = (EmotionGridViewAdapter) itemAdapter; if (position == emotionGvAdapter.getCount() - 1) { // 如果点击了最后一个回退按钮,则调用删除键事件 mEditText.dispatchKeyEvent(new KeyEvent( KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); } else { // 如果点击了表情,则添加到输入框中 String emotionName = emotionGvAdapter.getItem(position); // 获取当前光标位置,在指定位置上添加表情图片文本 int curPosition = mEditText.getSelectionStart(); StringBuilder sb = new StringBuilder(mEditText.getText().toString()); sb.insert(curPosition, emotionName); // 特殊文字处理,将表情等转换一下 mEditText.setText(SpanStringUtils.getEmotionContent(emotion_map_type, mContext, mEditText, sb.toString())); // 将光标设置到新增完表情的右侧 mEditText.setSelection(curPosition + emotionName.length()); } } } }; } }   代码相当简单,就是创建一个AdapterView.OnItemClickListener的全局监听器,然后在里面实现表情的输入与删除操作即可。那么怎么使用呢?我们在EmotionMainFragment类中使用创建GlobalOnItemClickManagerUtils,并绑定编辑框,部分代码如下: //创建全局监听 GlobalOnItemClickManagerUtils globalOnItemClickManager= GlobalOnItemClickManagerUtils.getInstance(getActivity()); if(isBindToBarEditText){ //绑定当前Bar的编辑框 globalOnItemClickManager.attachToEditText(bar_edit_text); }else{ // false,则表示绑定contentView, 此时外部提供的contentView必定也是EditText globalOnItemClickManager.attachToEditText((EditText) contentView); mEmotionKeyboard.bindToEditText((EditText)contentView); }   绑定的编辑框可能有两种情况,可能是Bar上的编辑框,但也可能是contentView,此时外部提供的contentView是EditText(可以直接理解为是把之前所说的listview替换成了edittext)。最后别忘记在EmotiomComplateFragment类种创建GridView时注册该监听器, //设置全局点击事件 gv.setOnItemClickListener(GlobalOnItemClickManagerUtils.getInstance(getActivity()).getOnItemClickListener(emotion_map_type)); 好了,到此本篇也完结了,点击源码地址下载,大家其他看源码吧,稍后我会上传一份到github。
http://www.itnose.net/detail/6619177.html 2016-08-05 11:36   [Android]

用法简单的呈现一组图片的框架(仿QQ/)

) NSInteger photosState;   /** 图片间距(默认为5) */ @property ( nonatomic , assign) CGFloat photoMargin; /** 图片的宽(正方形)默认为70 */ @property ( nonatomic
http://www.itnose.net/detail/6523695.html 2016-06-24 18:06   [其它]

  1  2  3  4  5 下一页>