Android-实现上下翻页的recyclerView

PageScrollHelper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
public class PagingScrollHelper {

RecyclerView mRecyclerView = null;

private MyOnScrollListener mOnScrollListener = new MyOnScrollListener();

private MyOnFlingListener mOnFlingListener = new MyOnFlingListener();
private int offsetY = 0;
private int offsetX = 0;

int startY = 0;
int startX = 0;


enum ORIENTATION {
HORIZONTAL, VERTICAL, NULL
}

private ORIENTATION mOrientation = ORIENTATION.HORIZONTAL;

public void setUpRecycleView(RecyclerView recycleView) {
if (recycleView == null) {
throw new IllegalArgumentException("recycleView must be not null");
}
mRecyclerView = recycleView;
//处理滑动
recycleView.setOnFlingListener(mOnFlingListener);
//设置滚动监听,记录滚动的状态,和总的偏移量
recycleView.setOnScrollListener(mOnScrollListener);
//记录滚动开始的位置
recycleView.setOnTouchListener(mOnTouchListener);
//获取滚动的方向
updateLayoutManger();

}

public void updateLayoutManger() {
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager != null) {
if (layoutManager.canScrollVertically()) {
mOrientation = ORIENTATION.VERTICAL;
} else if (layoutManager.canScrollHorizontally()) {
mOrientation = ORIENTATION.HORIZONTAL;
} else {
mOrientation = ORIENTATION.NULL;
}
if (mAnimator != null) {
mAnimator.cancel();
}
startX = 0;
startY = 0;
offsetX = 0;
offsetY = 0;

}

}

/**
* 获取总共的页数
*/
public int getPageCount() {
if (mRecyclerView != null) {
if (mOrientation == ORIENTATION.NULL) {
return 0;
}
if (mOrientation == ORIENTATION.VERTICAL && mRecyclerView.computeVerticalScrollExtent() != 0) {
return mRecyclerView.computeVerticalScrollRange() / mRecyclerView.computeVerticalScrollExtent();
} else if (mRecyclerView.computeHorizontalScrollExtent() != 0) {
Log.i("zzz","rang="+mRecyclerView.computeHorizontalScrollRange()+" extent="+mRecyclerView.computeHorizontalScrollExtent());
return mRecyclerView.computeHorizontalScrollRange() / mRecyclerView.computeHorizontalScrollExtent();
}
}
return 0;
}



ValueAnimator mAnimator = null;

public void scrollToPosition(int position) {
if (mAnimator == null) {
mOnFlingListener.onFling(0, 0);
}
if (mAnimator != null) {
int startPoint = mOrientation == ORIENTATION.VERTICAL ? offsetY : offsetX, endPoint = 0;
if (mOrientation == ORIENTATION.VERTICAL) {
endPoint = mRecyclerView.getHeight() * position;
} else {
endPoint = mRecyclerView.getWidth() * position;
}
if (startPoint != endPoint) {
mAnimator.setIntValues(startPoint, endPoint);
mAnimator.start();
}
}
}

public class MyOnFlingListener extends RecyclerView.OnFlingListener {

@Override
public boolean onFling(int velocityX, int velocityY) {
if (mOrientation == ORIENTATION.NULL) {
return false;
}
//获取开始滚动时所在页面的index
int p = getStartPageIndex();

//记录滚动开始和结束的位置
int endPoint = 0;
int startPoint = 0;

//如果是垂直方向
if (mOrientation == ORIENTATION.VERTICAL) {
startPoint = offsetY;

if (velocityY < 0) {
p--;
} else if (velocityY > 0) {
p++;
}
//更具不同的速度判断需要滚动的方向
//注意,此处有一个技巧,就是当速度为0的时候就滚动会开始的页面,即实现页面复位
endPoint = p * mRecyclerView.getHeight();

} else {
startPoint = offsetX;
if (velocityX < 0) {
p--;
} else if (velocityX > 0) {
p++;
}
endPoint = p * mRecyclerView.getWidth();

}
if (endPoint < 0) {
endPoint = 0;
}

//使用动画处理滚动
if (mAnimator == null) {
mAnimator = new ValueAnimator().ofInt(startPoint, endPoint);

mAnimator.setDuration(300);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int nowPoint = (int) animation.getAnimatedValue();

if (mOrientation == ORIENTATION.VERTICAL) {
int dy = nowPoint - offsetY;
//这里通过RecyclerView的scrollBy方法实现滚动。
mRecyclerView.scrollBy(0, dy);
} else {
int dx = nowPoint - offsetX;
mRecyclerView.scrollBy(dx, 0);
}
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//回调监听
if (null != mOnPageChangeListener) {
mOnPageChangeListener.onPageChange(getPageIndex());
}
//修复双击item bug
mRecyclerView.stopScroll();
startY = offsetY;
startX = offsetX;
}
});
} else {
mAnimator.cancel();
mAnimator.setIntValues(startPoint, endPoint);
}

mAnimator.start();

return true;
}
}

public class MyOnScrollListener extends RecyclerView.OnScrollListener {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
//newState==0表示滚动停止,此时需要处理回滚
if (newState == 0 && mOrientation != ORIENTATION.NULL) {
boolean move;
int vX = 0, vY = 0;
if (mOrientation == ORIENTATION.VERTICAL) {
int absY = Math.abs(offsetY - startY);
//如果滑动的距离超过屏幕的一半表示需要滑动到下一页
move = absY > recyclerView.getHeight() / 2;
vY = 0;

if (move) {
vY = offsetY - startY < 0 ? -1000 : 1000;
}

} else {
int absX = Math.abs(offsetX - startX);
move = absX > recyclerView.getWidth() / 2;
if (move) {
vX = offsetX - startX < 0 ? -1000 : 1000;
}

}

mOnFlingListener.onFling(vX, vY);

}

}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
//滚动结束记录滚动的偏移量
offsetY += dy;
offsetX += dx;
}
}

private MyOnTouchListener mOnTouchListener = new MyOnTouchListener();

private boolean firstTouch = true;

public class MyOnTouchListener implements View.OnTouchListener {

@Override
public boolean onTouch(View v, MotionEvent event) {
//手指按下的时候记录开始滚动的坐标
if (firstTouch) {
//第一次touch可能是ACTION_MOVE或ACTION_DOWN,所以使用这种方式判断
firstTouch = false;
startY = offsetY;
startX = offsetX;
}
if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
firstTouch = true;
}

return false;
}

}

private int getPageIndex() {
int p = 0;
if (mRecyclerView.getHeight() == 0 || mRecyclerView.getWidth() == 0) {
return p;
}
if (mOrientation == ORIENTATION.VERTICAL) {
p = offsetY / mRecyclerView.getHeight();
} else {
p = offsetX / mRecyclerView.getWidth();
}
return p;
}

private int getStartPageIndex() {
int p = 0;
if (mRecyclerView.getHeight() == 0 || mRecyclerView.getWidth() == 0) {
//没有宽高无法处理
return p;
}
if (mOrientation == ORIENTATION.VERTICAL) {
p = startY / mRecyclerView.getHeight();
} else {
p = startX / mRecyclerView.getWidth();
}
return p;
}

onPageChangeListener mOnPageChangeListener;

public void setOnPageChangeListener(onPageChangeListener listener) {
mOnPageChangeListener = listener;
}

public interface onPageChangeListener {
void onPageChange(int index);
}

}

使用

1
2
3
mScrollHelper.setUpRecycleView(mRv);
mScrollHelper.updateLayoutManger();
mScrollHelper.scrollToPosition(0);

scrollTo vs scrollBy

  1. 调用ViewGrop.scrollTo跟scrollBy方法移动的是改ViewGroup的子view。调用view的scrollBy跟to方法没有效果。
  2. to是相对初始位置的位移,by是相对于当前位置的位移。

参考:https://blog.csdn.net/guolin_blog/article/details/48719871

Android-自定义瀑布流的itemDecoration

point1

继承RecyclerView.ItemDecoration主要有以下三个方法:

  1. getItemOffsets(rect ,view ,recyclerView,state);

​ 设置绘制区域rect.set(x,x,x,x)。

  1. ondraw(cavas,recyclerview,state)

​ 找好可以绘制的区域,用cavas绘制。先于itemView的ondraw方法。

  1. ondrawover(cavas,recyclerview,state);

​ 这个跟ondraw差不多,后于itemView的ondraw方法。

point2 瀑布流的decoration

1
2
3
4
5
6
7
8
9
StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
int spanIndex = lp.getSpanIndex();
Logger.d("test>>>",position+"======"+spanIndex);

if(spanIndex%2==0){//left
outRect.set(0,0,mVerticalSpan,mVerticalSpan);
}else{//right
outRect.set(0,0,0,mVerticalSpan);
}

两列的时候,spanIndex要么为0,要么为1,0表示在左边,1表示在右边。

Android-自定义圆角ImageView

继承ImageView,重写onDraw方法,获取长跟宽之后使用clip方法裁剪圆角,之后调用super的onDraw。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class KJCornerImageView extends android.support.v7.widget.AppCompatImageView{
private float[] radiusArray = { 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f };

public KJCornerImageView(Context context) {
this(context,null);
}

public KJCornerImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}

public KJCornerImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.KJCornerImageView);
int leftBottom = typedArray.getDimensionPixelSize(R.styleable.KJCornerImageView_leftBottomCorner, 0);
int leftTop = typedArray.getDimensionPixelSize(R.styleable.KJCornerImageView_leftTopCorner, 0);
int rightBottom = typedArray.getDimensionPixelSize(R.styleable.KJCornerImageView_rightBottomCorner, 0);
int rightTop = typedArray.getDimensionPixelSize(R.styleable.KJCornerImageView_rightTopCorner, 0);
radiusArray[1] = leftTop;
radiusArray[2] = rightTop;
radiusArray[3] = rightTop;
radiusArray[4] = rightBottom;
radiusArray[5] = rightBottom;
radiusArray[6] = leftBottom;
radiusArray[7] = leftBottom;
typedArray.recycle();
}

public void setRadius(float leftTop, float rightTop, float rightBottom, float leftBottom) {
radiusArray[0] = leftTop;
radiusArray[1] = leftTop;
radiusArray[2] = rightTop;
radiusArray[3] = rightTop;
radiusArray[4] = rightBottom;
radiusArray[5] = rightBottom;
radiusArray[6] = leftBottom;
radiusArray[7] = leftBottom;

invalidate();
}

protected void onDraw(Canvas canvas) {
Path path = new Path();
path.addRoundRect(new RectF(0, 0, getWidth(), getHeight()), radiusArray, Path.Direction.CW);
canvas.clipPath(path);
super.onDraw(canvas);
}
}

leetcode-猜数字大小2

我们正在玩一个猜数游戏,游戏规则如下:

我从 1n 之间选择一个数字,你来猜我选了哪个数字。

每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了。

然而,当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。直到你猜到我选的数字,你才算赢得了这个游戏。

示例:

1
2
3
4
5
6
7
8
9
n = 10, 我选择了8.

第一轮: 你猜我选择的数字是5,我会告诉你,我的数字更大一些,然后你需要支付5块。
第二轮: 你猜是7,我告诉你,我的数字更大一些,你支付7块。
第三轮: 你猜是9,我告诉你,我的数字更小一些,你支付9块。

游戏结束。8 就是我选的数字。

你最终要支付 5 + 7 + 9 = 21 块钱。

给定一个 n ≥ 1,计算你至少需要拥有多少现金才能确保你能赢得这个游戏。

思路

记录a(i,j)表示从i到j的结果,那么有如下公式:

a(i,j)=min{ x+max{a(i,x-1),a(x+1,j)}} 其中x从i到j;

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public int getMoneyAmount(int n) {
//a(i,j)=min{ x+max{a(i,x-1),a(x+1,j)}}

int [][]a = new int[n+1][n+1];
for(int j =1;j<=n;j++){
for(int i =j;i>=1;i--){
if(i==j){
a[i][j]=0;
}else if(j-i==1){
a[i][j]=i;
}else{
int min=i+a[i+1][j];
for(int x=i+1;x<=j;x++){
int left = 0;
if(x-1>i){
left=a[i][x-1];
}
int right= 0;
if(x+1<j){
right=a[x+1][j];
}
int temp = x+(left>right?left:right);
min = temp>min?min:temp;
}
a[i][j]=min;
}
}
}
return a[1][n];
}
}

Android-瀑布流

point1

需要服务器返回图片规格

point2

在adapter的onBindViewHolder中根据服务端返回的图片的规格设置layoutParams

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void onBindViewHolder(@NonNull CusHolder holder, int position) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(mContext.getResources(), mData.get(position).score,options);
float f=options.outWidth/(float)options.outHeight;
int h= (int) (650/f);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, h);
holder.itemView.setLayoutParams(lp);

// holder.mIv.setImageResource(mData.get(position).score);
Glide.with(mContext).load(mData.get(position).score).into(holder.mIv);
}

point3

设置rv的manager为StaggeredGridLayoutManager

1
mRv.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL));

point4(不用设置)

  • 来回滑动的时候item交换位置:在设置layoutManager的时候,多加一句代码防止item位置交换 layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
  • 顶部留空的问题,在onScrollStateChanged里再加一句layoutManager.invalidateSpanAssignments();

参考:https://blog.csdn.net/qq_31390699/article/details/53465671

Android 特殊viewPager

point1

设置viewPager的clipChildPadding为false,并设置vp的padding;

1
2
3
4
5
6
<com.example.kb_jay.kjcusvp.KJCusVp
android:id="@+id/vp_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="40dp"
android:clipToPadding="false" />

point2

自定义PageTransformer;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static class KJTransformer implements PageTransformer {

private static final float SCALE = 0.8f;//表示放缩的倍数
private static final int TRANS=50;//表示平移的px数

@Override
public void transformPage(@NonNull View view, float position) {
float temp=Math.abs(position);
float scale = (1-(1- SCALE)*temp);
view.setScaleX(scale);
view.setScaleY(scale);

view.setTranslationX(-TRANS*position);

}
}

其中

  • view表示itemView
  • position表示itemView的位置(-1,0,1)

point3

设置viewPager的切换动画

1
mVp.setPageTransformer(false,new KJCusVp.KJTransformer());

源码地址:https://github.com/kbjay/KJCustomVp

Android-微信登陆

一、创建应用并成为开发者

1、创建应用

1)到微信开放平台管理中心,点击“创建移动应用”

2)填写基本的应用信息

3)填写平台信息

img

这里还是说一下怎么获取应用签名吧:

  1. 到“资源中心”–>“资源下载”–>“Android资源下载”,找到“签名生成工具”下载即可。飞机直达:Gen_Signature_Android2
  2. 将APP进行打包安装到手机(如果是在IDE中直接运行后安装的话,是使用debug签名的,跟上线后使用jks文件签名的不一样!!!)
  3. 给手机安装第一步中下载的签名工具,打开后输入APP的包名(包名是AndroidManifest.xml中package的值),点击生成,就可以看到应用签名了。

*注意:

签名工具得到的签名根你APP的打包步骤有关,默认的debug和jks文件签名,结果是不一样,如果app签名与微信开放平台的不一样,将无法调出微信登录界面!!!

4)最后提交,等待审核。(说是7天,一般1天后就审核通过了)

2、成为开发者

很简单,去“个人中心”,找到“开发者资质认证”,点击“申请”,然后一步步按要示填写信息,最后交钱就行,300大洋~~

img

二、微信登录

当审核通过之后,就可以开始使用微信开放平台提供的功能了。要使用“微信登录”功能必须成功开放者,也就是交了300块后默认开通此功能。

在这里可以得到AppId和AppSecret,记下,之后会用到。如下图所示:

img

1、引入微信SDK依赖

1)Android Studio:

在build.gradle的dependencies中加入以下语句

1
compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:1.0.2'

2)Eclipse(ADT):

去微信开放平台,“资源中心”–>“资源下载”–>“Android资源下载”,找到“开发工具包(SDK)”后下载。飞机直达:Android_SDK_4.0.2.zip

解压后找到lib文件夹,把wechat-sdk-android-with-mta-1.0.2.jar和wechat-sdk-android-without-mta-1.0.2.jar复制到工程lib文件夹下,rebuild一下。

2、申明应用权限

1
2
3
4
5
<uses-permission android:name="android.permission.INTERNET"/> 
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

3、向微信注册APP

在自定义的Application的onCreate中调用registToWX方法:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onCreate() {
super.onCreate();
registToWX();
}

private void registToWX() {
//AppConst.WEIXIN.APP_ID是指你应用在微信开放平台上的AppID,记得替换。
mWxApi = WXAPIFactory.createWXAPI(this, AppConst.WEIXIN.APP_ID, false);
// 将该app注册到微信
mWxApi.registerApp(AppConst.WEIXIN.APP_ID);
}

4、调用微信登录界面

一般会在登录界面会有一个微信图标,对微信图标设置点击事件,调用如下方法:

1
2
3
4
5
6
7
8
9
10
public void wxLogin() {
if (!MyApp.mWxApi.isWXAppInstalled()) {
UIUtils.showToast("您还未安装微信客户端");
return;
}
final SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
req.state = "diandi_wx_login";
MyApp.mWxApi.sendReq(req);
}

至此,就可以调用微信登录界面来进行登录认证了。

5、创建WXEntryActivity.java微信接收响应

WXEntryActivity是一个Activity,用来接收微信的响应信息。这里有几个需要注意的地方:

  1. 它必须在”包名.wxapi”这个包下,如:你的应用包名为:com.lqr.test,则WXEntryActivity所在的包名必须为com.lqr.test.wxapi。

  2. 创建后在AndroidManifest.xml文件中对WXEntryActivity进行设置:android:exported=”true”。

  3. WXEntryActivity.Java继承自Activity,实现IWXAPIEventHandler接口,该接口即处理微信和app通信的不同event。

以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
private static final int RETURN_MSG_TYPE_LOGIN = 1;
private static final int RETURN_MSG_TYPE_SHARE = 2;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//如果没回调onResp,八成是这句没有写
MyApp.mWxApi.handleIntent(getIntent(), this);
}

// 微信发送请求到第三方应用时,会回调到该方法
@Override
public void onReq(BaseReq req) {
}

// 第三方应用发送到微信的请求处理后的响应结果,会回调到该方法
//app发送消息给微信,处理返回消息的回调
@Override
public void onResp(BaseResp resp) {
LogUtils.sf(resp.errStr);
LogUtils.sf("错误码 : " + resp.errCode + "");
switch (resp.errCode) {

case BaseResp.ErrCode.ERR_AUTH_DENIED:
case BaseResp.ErrCode.ERR_USER_CANCEL:
if (RETURN_MSG_TYPE_SHARE == resp.getType()) UIUtils.showToast("分享失败");
else UIUtils.showToast("登录失败");
break;
case BaseResp.ErrCode.ERR_OK:
switch (resp.getType()) {
case RETURN_MSG_TYPE_LOGIN:
//拿到了微信返回的code,立马再去请求access_token
String code = ((SendAuth.Resp) resp).code;
LogUtils.sf("code = " + code);

//就在这个地方,用网络库什么的或者自己封的网络api,发请求去咯,注意是get请求

break;

case RETURN_MSG_TYPE_SHARE:
UIUtils.showToast("微信分享成功");
finish();
break;
}
break;
}
}
}

当微信授权第三登录后,会自动调用WXEntryActivity的onResp方法,可以在((SendAuth.Resp) resp).code得到“授权临时票据code”,之后可以通过code参数加上AppID和AppSecret等,通过API换取access_token,再通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

一般会把获取access_token步骤(包括之后的操作)放到服务器那边,服务器经过一番处理之后,返回用户信息给客户端。

三、踩坑经验

在app点击微信图标后,一片空白!!控制台打印错误码为-6。
原因:
这是因为app的使用的签名跟开放平台的应用签名不一致导致的。

解决方法:

  1. 使用签名工具得到手机中app的签名
  2. 修改开放平台上的应用签名
  3. 清除手机上微信APP的缓存信息(不知道的可以卸载后重装微信)
  4. 登录微信后,再点击APP的微信登录图标

参考:https://juejin.im/entry/58f709b68d6d8100649cade7

Android-tensorFlowDemo

https://github.com/kbjay/tensorflowDemo

  1. tensorFlow使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

public class TensorFlowImageClassifier implements Classifier {

private TensorFlowImageClassifier() {
}

public static Classifier create(
String modelPath,
String modelFilename,
String modelScoreFileName,
String[] labels,
int inputSize,
String inputName,
String outputName) {
TensorFlowImageClassifier c = new TensorFlowImageClassifier();
/********************声明input跟output的name*********************/
c.inputName = inputName;
c.outputName = outputName;
/*************************************************************/
c.tags = labels;
String path = Environment.getExternalStorageDirectory()+modelPath + modelFilename;
String pathScore = Environment.getExternalStorageDirectory()+modelPath + modelScoreFileName;
/********************初始化TensorFlowInferenceInterface****************/
try {
c.inferenceInterface = new TensorFlowInferenceInterface(new FileInputStream(new File(path)));
c.inferenceInterfaceScore = new TensorFlowInferenceInterface(new FileInputStream(new File(pathScore)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
/*************************************************************/
c.inputSize = inputSize;

/******************声明outPutsName***************************/
c.outputNames = new String[]{outputName};
/****************************************/

c.intValues = new int[inputSize * inputSize];
c.floatValues = new float[inputSize * inputSize * 3];

/******************初始化outPuts用来接受结果*************/
c.outputs = new float[labels.length];
c.outputScore= new float[2];
return c;
/***********************************************/
}

@Override
public List<Recognition> recognizeImage(final Bitmap bitmap) {
final ArrayList<Recognition> recognitions = new ArrayList<Recognition>();

bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
for (int i = 0; i < intValues.length; ++i) {
final int val = intValues[i];
floatValues[i * 3 + 0] = (((val >> 16) & 0xFF)) / 128f - 1.0f;
floatValues[i * 3 + 1] = (((val >> 8) & 0xFF)) / 128f - 1.0f;
floatValues[i * 3 + 2] = ((val & 0xFF)) / 128f - 1.0f;
}
/*********************分别对应输入,运行,输出****************************/
inferenceInterface.feed(inputName, floatValues, 1, inputSize, inputSize, 3);
inferenceInterface.run(outputNames, logStats);
inferenceInterface.fetch(outputName, outputs);
/***********************************************************/
int index = 0;
float maxPercent = 0f;
for (int i = 0; i < outputs.length; i++) {
if (maxPercent < outputs[i]) {
maxPercent = outputs[i];
index = i;
}
}
recognitions.add(new Recognition( "LABEL", tags[index], maxPercent, null));

inferenceInterfaceScore.feed(inputName,floatValues,1,inputSize,inputSize,3);
inferenceInterfaceScore.run(outputNames,logStats);
inferenceInterfaceScore.fetch(outputName,outputScore);
recognitions.add(new Recognition("SCORE","GOOD="+outputScore[0]+" POOR="+outputScore[1],null,null));

return recognitions;
}

@Override
public void enableStatLogging(boolean logStats) {
this.logStats = logStats;
}

@Override
public String getStatString() {
return inferenceInterface.getStatString();
}

@Override
public void close() {
inferenceInterface.close();
}
}
  1. 截取bitmap之后缩放

    参考:https://www.jianshu.com/p/da10fcc9d0c4

1
2
3
4
5
6
7
8
9
10
11
12
13
private static Bitmap getScaleBitmap(Bitmap bitmap, int size) throws IOException {

int width = bitmap.getWidth();
int height = bitmap.getHeight();
int startx = width>height?(width-height)/2:0;
int starty = width>height?0:(height-width)/2;
int cropSize=width>height?height:width;

float scaleSize = ((float) size) / cropSize;
Matrix matrix = new Matrix();
matrix.postScale(scaleSize, scaleSize);
return Bitmap.createBitmap(bitmap, startx, starty, cropSize, cropSize, matrix, true);
}

​ createBitmap:

1
2
3
4
5
6
7
8
9
10
11
12
/*
* @param source 需要裁剪的bitmap
* @param x 裁剪x点起始坐标
* @param y 裁剪y点起始坐标
* @param width 裁剪bitmap的宽度
* @param height 裁剪bitmap的高度
* @param m 针对裁剪后的bitmap进行矩阵缩放(可选)
* @param filter true(对原bitmap进行裁剪操作)
* @return A 返回一个裁剪过的bitmap 或者为操作过的(原)bitmap
*/
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
Matrix m, boolean filter)

leetcode-计算各个位数不同的数字个数

给定一个非负整数 n,计算各位数字都不同的数字 x 的个数,其中 0 ≤ x < 10n。

示例:
给定 n = 2,返回 91。(答案应该是除[11,22,33,44,55,66,77,88,99]外,0 ≤ x < 100 间的所有数字)

致谢:
特别感谢 @memoryless 添加这个题目并创建所有测试用例。

思路

1
2
3
4
5
6
7
8
//         n 0 :0
// n 1:0~9
// n 2:0~9 + 10~99
// n 3:0~9 + 10~99 + 100~999
// n 4:0~9 + 10~99 + 100~999 + 1000~9999


每一位从0910种选择,注意第一位不能为0

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public int countNumbersWithUniqueDigits(int n) {
if(n==0){
return 1;
}
if(n==1){
return 10;
}
int []arr = new int[n+1];
arr[0]=1;
arr[1]=10;
for(int i =2;i<n+1;i++){
int temp=1;
for(int j =0;j<i;j++){
if(j==0){
temp*=9;
}else{
temp*=(10-j);
}

}
arr[i]= arr[i-1]+temp;
System.out.println(arr[i]);
}
return arr[n];
}
}

Android-查找讯飞输入法下载的离线资源02

目标

找到跟离线包相关的所有下载的文件

结果

  1. 下载的是个zip,解压之后是两个so。这个就是它下载的所有东西了。
  2. 下面是截取的下载地址:

http://183.91.33.82/download.voicecloud.cn/ygxt/20161025/c8f798bb-f87d-4860-a6b9-3f64275af70d.zip

1
07-03 19:12:56.533 17441-18271/com.iflytek.inputmethod.assist D/iFlyIME_Network: [970] alm.a: HTTP response for request=<[ ] http://download.voicecloud.cn/ygxt/20161025/c8f798bb-f87d-4860-a6b9-3f64275af70d.zip 0xade53710 NORMAL null> [lifetime=22703], [size=null], [rc=200], [retryCount=0]
  1. 下载的zip文件会先放在sd的IflyIme下的download文件夹下面,之后才会放到之前找到的隐藏目录下。
1
7-03 19:12:56.533 17441-17441/com.iflytek.inputmethod.assist D/DownloadHandleManager: onFinish filename : /storage/emulated/0/iFlyIME/Download/c8f798bb-f87d-4860-a6b9-3f64275af70d.zip

实验成功过程

  1. 修改smali文件(Logging.smali)
  2. 新建一个injectLog.smali文件添加到工程中,通过python脚本给downLoad下所有的smali文件的每个方法添加输出日志。可以看到方法调用的顺序。参考https://blog.csdn.net/charlessimonyi/article/details/52027563
  3. 重新打包
  4. 重新签名
  5. 安装
  6. 查看assist进程下的日志,可以看到下载进度,完成之后可以看到下载路径。且下载的zip包跟提示消耗的流量一致,进一步可以确认下载的只有这两个so文件。

反编译思路

  1. 利用dex2jar+jd-gui 获取class文件
  2. 利用apkTool获取smali文件跟资源文件
  3. 利用log注入给指定包名下的smali文件中的方法注入log:参考https://blog.csdn.net/charlessimonyi/article/details/52027563
  4. 触发关注的事件,观察log,找出相关的类,阅读java代码
  5. 在对应的smali文件中加断点调试代码 参考:https://kbjay.github.io/2018/06/29/Android-%E5%8F%8D%E7%BC%96%E8%AF%91%E8%B0%83%E8%AF%95smali%E6%96%87%E4%BB%B6/

ps

在查找关键词的过程中发现了Aitakl5.class,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class Aitalk5
{
...

private static native int JniAddLexiconItem(String paramString, int paramInt);

private static native int JniAddLexiconItemPhoneme(String paramString1, String paramString2, int paramInt);

private static native int JniAppendData(byte[] paramArrayOfByte, int paramInt);

private static native int JniBeginLexicon(String paramString);

private static native int JniBuildGrammar(byte[] paramArrayOfByte, int paramInt);

private static native int JniCreate(String paramString, int paramInt);

private static native int JniDestroy();

private static native int JniEndData();

private static native int JniEndLexicon(String paramString);

private static native int JniGetConfidence(int paramInt);

private static native int JniGetEngineState();

private static native int JniGetItemId(int paramInt1, int paramInt2, int paramInt3);

private static native int JniGetItemNumber(int paramInt1, int paramInt2);

private static native String JniGetItemText(int paramInt1, int paramInt2, int paramInt3);

private static native int JniGetResCount();

private static native int JniGetSlotConfidence(int paramInt1, int paramInt2);

private static native String JniGetSlotName(int paramInt1, int paramInt2);

private static native int JniGetSlotNumber(int paramInt);

private static native int JniLoadNetwork(String paramString);

private static native int JniOnReadResource(byte[] paramArrayOfByte, int paramInt1, int paramInt2);

private static native int JniRunTask();

private static native int JniSetHotWords(String paramString, String[] paramArrayOfString, int paramInt);

private static native int JniSetParam(int paramInt1, int paramInt2);

private static native void JniStart(String paramString);

private static native int JniStop();

private static native int JniUnLoadNetwork(String paramString);

private static native int JniUpdateGrammar(String paramString);

...
}