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 支持
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐