android实现调用deepseek,获取到结果后,定时拼接显示返回内容,页面随文本增加,屏幕移动到文本底部
deepseek
·
Android需要实现类似于网页版deepseek的效果,定时拼接显示返回的内容,并且屏幕随着文本内容的增加,一直保持在文本内容的底部。之前遇到的问题,是在文本没有超出屏幕的时候,正常,超出的话,偶现屏幕闪烁的问题。
首先要把recylerView的动画关掉
recycler_view.setItemAnimator(null);
然后是滑动到底部的代码
private void scrollToBottomSmoothly() {
recycler_view.post(() -> {
LinearLayoutManager layoutManager = (LinearLayoutManager) recycler_view.getLayoutManager();
int lastPos = adapter.getItemCount() - 1;
// 方法1:直接滚动(无动画)
// layoutManager.scrollToPositionWithOffset(lastPos, 0);
// // 方法2:精确滚动(适用于长内容)
recycler_view.postDelayed(() -> {
View lastChild = layoutManager.findViewByPosition(lastPos);
if (lastChild != null) {
int scrollNeeded = lastChild.getBottom() - recycler_view.getHeight();
if (scrollNeeded > 0) {
recycler_view.scrollBy(0, scrollNeeded);
}
}
}, 100); // 缩短延迟时间
});
}
这里边提供两个方法,根据需要选择,千万别两个都打开,要不然会闪烁的厉害
deepseek方法封装:
文档:首次调用 API
public class DeepSeekUtils {
private String api_key = "xxxxxx";
public void requestDeepSeek(String content,IDeepSeekResult iDeepSeekResult) {
DeepSeekBean deepSeekBean=new DeepSeekBean();
deepSeekBean.setMax_tokens(2048);
deepSeekBean.setFrequency_penalty(0);
deepSeekBean.setModel("deepseek-chat");
deepSeekBean.setStream(true);
DeekMessages deekMessages=new DeekMessages();
deekMessages.setContent(content);
deekMessages.setRole("user");
deepSeekBean.setMessages(Arrays.asList(deekMessages));
OkHttpClient client = new OkHttpClient().newBuilder()
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, new Gson().toJson(deepSeekBean));
Request request = new Request.Builder()
.url("https://api.deepseek.com/chat/completions")
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("Authorization", "Bearer "+api_key)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 请求失败
Logger.show("Request failed: ", e.getMessage());
}
@Override
public void onResponse(Call call, Response responseResult) throws IOException {
if (responseResult.isSuccessful()) {
// 请求成功
String jsonData = responseResult.body().string();
// Logger.show("Response: ", jsonData);
// 分割 JSON 数据
String[] dataChunks = jsonData.split("data: ");
Gson gson = new Gson();
List<DeepSeekResponse> responses = new ArrayList<>();
for (String chunk : dataChunks) {
chunk = chunk.trim();
if (!chunk.isEmpty() &&!chunk.equals("[DONE]")) {
try {
// 解析 JSON 数据
DeepSeekResponse response = gson.fromJson(chunk, DeepSeekResponse.class);
responses.add(response);
} catch (JsonSyntaxException e) {
System.err.println("JSON 解析错误: " + e.getMessage());
}
}
}
if(iDeepSeekResult!=null){
iDeepSeekResult.deepSeekResult(responses);
}
} else {
// 请求失败,处理错误状态码
Logger.show("Unexpected response: ", responseResult.message());
if(iDeepSeekResult!=null){
iDeepSeekResult.deepSeekFail( responseResult.message());
}
}
}
});
}
public interface IDeepSeekResult{
void deepSeekResult( List<DeepSeekResponse> responses);
void deepSeekFail(String error);
}
}
涉及到的实体类:
// 对应 choices 数组中的对象
public class Choice {
private int index;
private Delta delta;
// Getters and Setters
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public Delta getDelta() {
return delta;
}
public void setDelta(Delta delta) {
this.delta = delta;
}
}
public class DeekMessages {
private String content;
private String role;
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setRole(String role) {
this.role = role;
}
public String getRole() {
return role;
}
}
public class DeepSeekBean {
private List<DeekMessages> messages;
private String model;
private int frequency_penalty;
private int max_tokens;
private boolean stream;
public void setMessages(List<DeekMessages> messages) {
this.messages = messages;
}
public List<DeekMessages> getMessages() {
return messages;
}
public void setModel(String model) {
this.model = model;
}
public String getModel() {
return model;
}
public void setFrequency_penalty(int frequency_penalty) {
this.frequency_penalty = frequency_penalty;
}
public int getFrequency_penalty() {
return frequency_penalty;
}
public void setMax_tokens(int max_tokens) {
this.max_tokens = max_tokens;
}
public int getMax_tokens() {
return max_tokens;
}
public void setStream(boolean stream) {
this.stream = stream;
}
public boolean getStream() {
return stream;
}
}
// 对应整个 JSON 对象
public class DeepSeekResponse {
private String id;
private String object;
private long created;
private String model;
private String system_fingerprint;
private List<Choice> choices;
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getObject() {
return object;
}
public void setObject(String object) {
this.object = object;
}
public long getCreated() {
return created;
}
public void setCreated(long created) {
this.created = created;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getSystem_fingerprint() {
return system_fingerprint;
}
public void setSystem_fingerprint(String system_fingerprint) {
this.system_fingerprint = system_fingerprint;
}
public List<Choice> getChoices() {
return choices;
}
public void setChoices(List<Choice> choices) {
this.choices = choices;
}
}
public class Delta {
private String role;
private String content;
// Getters and Setters
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
deepseek会一次把所有数据给出来,如果在页面上也是这样显示,不太友好,应该跟网页版一样,定时读取,然后拼接起来
给出所有代码
private int counter = 0;
private static final int DELAY_MILLIS = 50; // 100ms
private List<AudioResponse> dataEntityList = new ArrayList<>();
private AudioResponseAdapter adapter;
// 初始化列表
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recycler_view.setLayoutManager(linearLayoutManager);
//recycler_view.setHasFixedSize(true);
recycler_view.setItemAnimator(null);
recycler_view.setHasFixedSize(false); // 允许动态调整大小
recycler_view.setItemViewCacheSize(20); // 增加缓存
//recycler_view.setItemAnimator(null); // 禁用默认动画
recycler_view.addItemDecoration(new SpacesItemDecoration(ConvertUtils.dp2px(this, 5)));
adapter = new AudioResponseAdapter(dataEntityList,VoiceResponseActivity.this);
recycler_view.setAdapter(adapter);
private void setDeepSeek(String question){
if(StringUtils.isEmpty(question)){
ToastUtils.show(R.string.please_enter);
return;
}
AudioResponse audioResponse=new AudioResponse();
audioResponse.setToMessage(question);
adapter.updateData(audioResponse);
scrollToBottomSmoothly();
DeepSeekUtils deepSeekUtils = new DeepSeekUtils();
try {
startLoadingAnimation();
deepSeekUtils.requestDeepSeek(question, new DeepSeekUtils.IDeepSeekResult() {
@Override
public void deepSeekResult(List<DeepSeekResponse> responses) {
Log.i("deepSeekResult",responses.size()+"");
AudioResponse finalAudioResponse = new AudioResponse();
runOnUiThread(() -> {
adapter.updateDataWhenFromMessageAppend(finalAudioResponse, "", true);
});
List<String> contentList = new ArrayList<>();
for (DeepSeekResponse response : responses) {
for (Choice choice : response.getChoices()) {
if (choice.getDelta() != null && choice.getDelta().getContent() != null) {
contentList.add(choice.getDelta().getContent());
}
}
}
counter = 0;
final Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (counter < contentList.size()) {
String string = contentList.get(counter);
runOnUiThread(() -> {
adapter.updateDataWhenFromMessageAppend(finalAudioResponse, string, false);
scrollToBottomSmoothly();
});
counter++;
if (counter < contentList.size()) {
mainHandler.postDelayed(this, DELAY_MILLIS);
} else {
runOnUiThread(() -> stopLoadingAnimation(""));
}
}
}
}, DELAY_MILLIS);
}
@Override
public void deepSeekFail(String error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
stopLoadingAnimation(getRsString(R.string.retrieve));
iv_sync_data.setVisibility(View.VISIBLE);
}
});
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void scrollToBottomSmoothly() {
recycler_view.post(() -> {
LinearLayoutManager layoutManager = (LinearLayoutManager) recycler_view.getLayoutManager();
int lastPos = adapter.getItemCount() - 1;
// 方法1:直接滚动(无动画)
// layoutManager.scrollToPositionWithOffset(lastPos, 0);
// // 方法2:精确滚动(适用于长内容)
recycler_view.postDelayed(() -> {
View lastChild = layoutManager.findViewByPosition(lastPos);
if (lastChild != null) {
int scrollNeeded = lastChild.getBottom() - recycler_view.getHeight();
if (scrollNeeded > 0) {
recycler_view.scrollBy(0, scrollNeeded);
}
}
}, 100); // 缩短延迟时间
});
}
public class AudioResponseAdapter extends RecyclerView.Adapter<AudioResponseAdapter.StrokeHolder> {
private OnItemClickListener onItemClickListener;
private List<AudioResponse> dataEntityList = new ArrayList<>();
private Markwon markwon;
public AudioResponseAdapter(List<AudioResponse> dataEntityList,Context mContext) {
this.dataEntityList = dataEntityList;
markwon = Markwon.create(mContext);;
}
@Override
public StrokeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = View.inflate(parent.getContext(), R.layout.item_audio_response_layout, null);
return new StrokeHolder(view, onItemClickListener);
}
@Override
public void onBindViewHolder(StrokeHolder holder, int position) {
AudioResponse resultEntity = dataEntityList.get(position);
holder.setData(resultEntity);
}
@Override
public int getItemCount() {
return dataEntityList.size();
}
public AudioResponse getItemBean(int position) {
return dataEntityList.get(position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void updateData(AudioResponse audioBean) {
dataEntityList.add(audioBean);
notifyDataSetChanged();
}
StringBuilder stringBuilder=new StringBuilder();
public void updateDataWhenFromMessageAppend(AudioResponse audioBean,String message,boolean needAddLast) {
stringBuilder.setLength(0);
if (needAddLast) {
dataEntityList.add(audioBean);
notifyItemInserted(dataEntityList.size() - 1); // 使用insert通知
} else {
int lastPos = dataEntityList.size() - 1;
AudioResponse lastItem = dataEntityList.get(lastPos);
stringBuilder.setLength(0);
stringBuilder.append(lastItem.getFromMessage() == null ? "" : lastItem.getFromMessage());
stringBuilder.append(message);
lastItem.setFromMessage(stringBuilder.toString());
// 使用不带payload的更新(Markwon需要完整刷新)
notifyItemChanged(lastPos);
}
}
@Override
public int getItemViewType(int position) {
return position;
}
public void clearData() {
dataEntityList.clear();
notifyDataSetChanged();
}
class StrokeHolder extends RecyclerView.ViewHolder implements View.OnClickListener ,View.OnLongClickListener{
TextView tvAudioInfo;
TextView tvRight;
LinearLayout ll_right;
LinearLayout ll_left;
OnItemClickListener onItemClick;
public StrokeHolder(View itemView, OnItemClickListener onItemClickListener) {
super(itemView);
tvAudioInfo = (TextView) itemView.findViewById(R.id.tv_audio_info);
tvRight= (TextView) itemView.findViewById(R.id.tv_right);
ll_right=itemView.findViewById(R.id.ll_right);
ll_left=itemView.findViewById(R.id.ll_left);
}
public void setData(AudioResponse resultEntity) {
if(!StringUtils.isEmpty(resultEntity.getFromMessage())){
markwon.setMarkdown(tvAudioInfo,resultEntity.getFromMessage());
// tvAudioInfo.setText(resultEntity.getFromMessage());
ll_left.setVisibility(View.VISIBLE);
ll_right.setVisibility(View.GONE);
}else{
tvRight.setText(resultEntity.getToMessage());
ll_left.setVisibility(View.GONE);
ll_right.setVisibility(View.VISIBLE);
}
}
@Override
public void onClick(View v) {
// if (onItemClick != null) {
// onItemClick.onItemClick(dataEntityList.get(getPosition()), getPosition());
// }
}
@Override
public boolean onLongClick(View v) {
return true;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:gravity="center_vertical"
android:paddingBottom="@dimen/dp_8"
android:paddingTop="@dimen/dp_8"
android:layout_marginStart="@dimen/dp_5"
android:layout_marginEnd="@dimen/dp_5"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/ll_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/icon_voice_left"/>
<TextView
android:id="@+id/tv_audio_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="我的问题"
android:background="@drawable/box_white_bg_radius2"
android:textColor="#000000"
android:padding="@dimen/dp_5"
android:textSize="@dimen/dp_14" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/ll_left"
android:gravity="center"
android:layout_alignParentEnd="true"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="我的问题"
android:background="@drawable/box_green_bg_radius"
android:textColor="#000000"
android:padding="@dimen/dp_5"
android:textSize="@dimen/dp_14" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
implementation 'io.noties.markwon:html:4.6.2' // 如果需要 HTML 支持
更多推荐


所有评论(0)