鸿蒙5新闻列表页实战:导航栏+卡片列表+分页加载

暗雨OL
发布于 2025-6-27 21:50
浏览
0收藏

一、整体架构设计
页面组成结构

  1. 顶部导航栏

    • 应用标题
    • 搜索按钮
    • 用户头像
  2. 分类标签栏

    • 可横向滚动的分类标签
    • 当前选中标签高亮
  3. 新闻卡片列表

    • 卡片式布局
    • 图片 + 标题 + 来源 + 时间
    • 交互反馈效果
  4. 分页加载控制

    • 滚动到底部自动加载
    • 加载中状态提示
    • 无更多数据提示
      技术实现方案
  • 使用DirectionalLayout作为主容器
  • TabList实现分类标签栏
  • ListContainer展示新闻列表
  • PageSliderProvider实现分页加载
  • 自定义NewsCard组件
  • 骨架屏优化加载体验
    二、XML布局实现
  1. 主页面布局 (news_list_page.xml)
    <?xml version=“1.0” encoding=“utf-8”?>
    <DirectionalLayout
    xmlns:ohos=“http://schemas.huawei.com/res/ohos
    ohos:width=“match_parent”
    ohos:height=“match_parent”
    ohos:orientation=“vertical”>

    <!-- 顶部导航栏 -->
    <DirectionalLayout
    ohos:id=“$+id:app_bar”
    ohos:width=“match_parent”
    ohos:height=“56vp”
    ohos:orientation=“horizontal”
    ohos:padding=“12vp”
    ohos:background_element=“?attr/colorPrimary”>

     <Text
         ohos:width="0vp"
         ohos:height="match_content"
         ohos:weight="1"
         ohos:text="新闻资讯"
         ohos:text_size="22fp"
         ohos:text_color="white"
         ohos:text_font="sans-serif-medium"/>
         
     <Image
         ohos:width="32vp"
         ohos:height="32vp"
         ohos:image_src="$media:ic_search"
         ohos:margin="0 16vp"
         ohos:tint="white"/>
         
     <Image
         ohos:width="32vp"
         ohos:height="32vp"
         ohos:image_src="$media:ic_user"
         ohos:corner_radius="16vp"
         ohos:tint="white"/>
    

    </DirectionalLayout>

    <!-- 分类标签栏 -->
    <TabList
    ohos:id=“$+id:tab_list”
    ohos:width=“match_parent”
    ohos:height=“48vp”
    ohos:margin=“0 12vp”
    ohos:orientation=“horizontal”
    ohos:scrollable=“true”
    ohos:selected_tab_background=“?attr/colorPrimarySurface”
    ohos:normal_tab_text_color=“#666666”
    ohos:selected_tab_text_color=“?attr/colorPrimary”/>

    <!-- 新闻列表区域 -->
    <ListContainer
    ohos:id=“$+id:news_list”
    ohos:width=“match_parent”
    ohos:height=“match_parent”
    ohos:divider=“1vp”
    ohos:divider_color=“#10000000”
    ohos:margin=“4vp 0”
    ohos:rebound_effect=“true”/>

    <!-- 底部加载状态 -->
    <DirectionalLayout
    ohos:id=“$+id:loading_container”
    ohos:width=“match_parent”
    ohos:height=“56vp”
    ohos:visibility=“invisible”
    ohos:orientation=“horizontal”
    ohos:gravity=“center”>

     <ProgressBar
         ohos:width="24vp"
         ohos:height="24vp"
         ohos:margin="0 8vp"
         ohos:progress_color="?attr/colorPrimary"/>
         
     <Text
         ohos:width="match_content"
         ohos:height="match_content"
         ohos:text="加载中..."
         ohos:text_size="16fp"
         ohos:text_color="#666666"/>
    

    </DirectionalLayout>
    </DirectionalLayout>

  2. 新闻卡片布局 (item_news_card.xml)
    <?xml version=“1.0” encoding=“utf-8”?>
    <DirectionalLayout
    xmlns:ohos=“http://schemas.huawei.com/res/ohos
    ohos:width=“match_parent”
    ohos:height=“140vp”
    ohos:orientation=“horizontal”
    ohos:padding=“12vp”
    ohos:background_element=“#FFFFFF”
    ohos:clickable=“true”
    ohos:focusable=“true”
    ohos:margin=“4vp 8vp”
    ohos:corner_radius=“12vp”
    ohos:border=“#10000000”
    ohos:border_width=“1vp”>

    <Image
    ohos:id=“$+id:news_image”
    ohos:width=“120vp”
    ohos:height=“match_parent”
    ohos:image_src=“$media:placeholder”
    ohos:scale_mode=“center_crop”
    ohos:corner_radius=“8vp”/>

    <DirectionalLayout
    ohos:width=“0vp”
    ohos:height=“match_parent”
    ohos:weight=“1”
    ohos:orientation=“vertical”
    ohos:margin=“12vp”>

     <Text
         ohos:id="$+id:news_title"
         ohos:width="match_parent"
         ohos:height="match_content"
         ohos:text="新闻标题将在这里显示..."
         ohos:text_size="18fp"
         ohos:text_font="sans-serif-medium"
         ohos:text_color="#333333"
         ohos:max_text_lines="2"
         ohos:auto_font_size="true"/>
         
     <Text
         ohos:id="$+id:news_source"
         ohos:width="match_content"
         ohos:height="match_content"
         ohos:text="来源"
         ohos:text_size="14fp"
         ohos:text_color="#666666"
         ohos:margin="8vp 0 0 0"/>
         
     <Text
         ohos:id="$+id:news_time"
         ohos:width="match_content"
         ohos:height="match_content"
         ohos:text="5分钟前"
         ohos:text_size="14fp"
         ohos:text_color="#999999"
         ohos:margin="4vp 0 0 0"/>
         
     <DirectionalLayout
         ohos:width="match_parent"
         ohos:height="0vp"
         ohos:orientation="horizontal"
         ohos:gravity="center"
         ohos:margin="8vp 0 0 0"
         ohos:weight="1">
         
         <Image
             ohos:width="16vp"
             ohos:height="16vp"
             ohos:image_src="$media:ic_comment"
             ohos:tint="#999999"/>
             
         <Text
             ohos:id="$+id:comment_count"
             ohos:width="match_content"
             ohos:height="match_content"
             ohos:text="128"
             ohos:text_size="14fp"
             ohos:text_color="#999999"
             ohos:margin="0 0 0 4vp"/>
     </DirectionalLayout>
    

    </DirectionalLayout>
    </DirectionalLayout>

  3. 骨架屏布局 (item_skeleton.xml)
    <?xml version=“1.0” encoding=“utf-8”?>
    <DirectionalLayout
    xmlns:ohos=“http://schemas.huawei.com/res/ohos
    ohos:width=“match_parent”
    ohos:height=“140vp”
    ohos:orientation=“horizontal”
    ohos:padding=“12vp”
    ohos:background_element=“#FFFFFF”
    ohos:margin=“4vp 8vp”
    ohos:corner_radius=“12vp”>

    <!-- 图片占位 -->
    <ShapeElement
    ohos:width=“120vp”
    ohos:height=“match_parent”
    ohos:corner_radius=“8vp”
    ohos:background_element=“#F0F0F0”/>

    <DirectionalLayout
    ohos:width=“0vp”
    ohos:height=“match_parent”
    ohos:weight=“1”
    ohos:orientation=“vertical”
    ohos:margin=“12vp”>

     <!-- 标题占位 -->
     <ShapeElement 
         ohos:width="match_parent"
         ohos:height="24vp"
         ohos:corner_radius="4vp"
         ohos:background_element="#F0F0F0"/>
     
     <!-- 副标题占位 -->
     <ShapeElement 
         ohos:width="40vp"
         ohos:height="16vp"
         ohos:corner_radius="4vp"
         ohos:background_element="#F0F0F0"
         ohos:top_margin="16vp"/>
     
     <!-- 时间占位 -->
     <ShapeElement 
         ohos:width="70vp"
         ohos:height="16vp"
         ohos:corner_radius="4vp"
         ohos:background_element="#F0F0F0"
         ohos:top_margin="8vp"/>
    

    </DirectionalLayout>
    </DirectionalLayout>
    三、Java业务逻辑实现

  4. 数据模型定义
    public class NewsItem {
    private String id;
    private String title;
    private String imageUrl;
    private String source;
    private String publishTime;
    private int commentCount;

    // 构造函数
    public NewsItem(String id, String title, String imageUrl,
    String source, String publishTime, int commentCount) {
    this.id = id;
    this.title = title;
    this.imageUrl = imageUrl;
    this.source = source;
    this.publishTime = publishTime;
    this.commentCount = commentCount;
    }

    // Getters
    public String getId() { return id; }
    public String getTitle() { return title; }
    public String getImageUrl() { return imageUrl; }
    public String getSource() { return source; }
    public String getPublishTime() { return publishTime; }
    public int getCommentCount() { return commentCount; }
    }

public class NewsCategory {
private String id;
private String name;
private boolean selected;

public NewsCategory(String id, String name, boolean selected) {
    this.id = id;
    this.name = name;
    this.selected = selected;
}

// Getters & Setters
public String getId() { return id; }
public String getName() { return name; }
public boolean isSelected() { return selected; }
public void setSelected(boolean selected) { this.selected = selected; }

}
2. 新闻列表适配器
public class NewsListAdapter extends PageSliderProvider {
private static final int ITEM_TYPE_NEWS = 0;
private static final int ITEM_TYPE_LOADING = 1;

private Context context;
private List<NewsItem> newsList = new ArrayList<>();
private boolean isLoading = false;
private boolean hasMore = true;

public NewsListAdapter(Context context) {
    this.context = context;
}

public void setLoading(boolean loading) {
    isLoading = loading;
}

public void setHasMore(boolean hasMore) {
    this.hasMore = hasMore;
}

public void updateData(List<NewsItem> newItems, boolean reset) {
    if (reset) {
        newsList.clear();
    }
    newsList.addAll(newItems);
}

@Override
public int getCount() {
    // 底部加载提示占位
    return newsList.size() + (isLoading || !hasMore ? 1 : 0);
}

@Override
public Object createPageInContainer(ComponentContainer container, int position) {
    Component component;
    if (position < newsList.size()) {
        // 新闻卡片
        component = LayoutScatter.getInstance(context)
            .parse(ResourceTable.Layout_item_news_card, null, false);
        bindNewsData(component, newsList.get(position));
    } else {
        // 加载状态
        if (isLoading) {
            component = LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_item_loading, null, false);
        } else {
            component = LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_item_no_more, null, false);
        }
    }
    container.addComponent(component);
    return component;
}

@Override
public void destroyPageFromContainer(ComponentContainer container, int position, Object object) {
    Component component = (Component) object;
    container.removeComponent(component);
}

@Override
public boolean isPageMatchToObject(Component component, Object object) {
    return component == object;
}

private void bindNewsData(Component component, NewsItem newsItem) {
    Text title = (Text) component.findComponentById(ResourceTable.Id_news_title);
    title.setText(newsItem.getTitle());
    
    Image image = (Image) component.findComponentById(ResourceTable.Id_news_image);
    loadImage(image, newsItem.getImageUrl());
    
    Text source = (Text) component.findComponentById(ResourceTable.Id_news_source);
    source.setText(newsItem.getSource());
    
    Text time = (Text) component.findComponentById(ResourceTable.Id_news_time);
    time.setText(formatTime(newsItem.getPublishTime()));
    
    Text comments = (Text) component.findComponentById(ResourceTable.Id_comment_count);
    comments.setText(String.valueOf(newsItem.getCommentCount()));
    
    // 点击事件
    component.setClickedListener(c -> openNewsDetail(newsItem));
}

private void loadImage(Image imageView, String url) {
    // 使用鸿蒙ImageLoader加载图片
    ImageLoader.loadImage(context, imageView, url, ResourceTable.Media_placeholder);
}

private String formatTime(String time) {
    // 时间格式化逻辑
    return TimeUtil.getFriendlyTimeSpanByNow(time);
}

private void openNewsDetail(NewsItem item) {
    Intent intent = new Intent();
    Operation operation = new Intent.OperationBuilder()
        .withDeviceId("")
        .withBundleName(context.getBundleName())
        .withAbilityName(NewsDetailAbility.class.getName())
        .build();
    intent.setOperation(operation);
    intent.setParam("news_id", item.getId());
    context.startAbility(intent, 0);
}

}
3. 主页面逻辑实现
public class NewsListAbilitySlice extends AbilitySlice {
private static final int PAGE_SIZE = 10;
private static final String TAG = “NewsListAbilitySlice”;

private TabList tabList;
private ListContainer newsList;
private DirectionalLayout loadingContainer;
private NewsListAdapter adapter;
private List<NewsCategory> categories = new ArrayList<>();
private int currentCategoryIndex = 0;
private int currentPage = 1;

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    setUIContent(ResourceTable.Layout_news_list_page);
    
    // 初始化视图
    initViews();
    
    // 加载分类数据
    loadCategories();
    
    // 加载第一页数据
    loadNews(true);
}

private void initViews() {
    tabList = (TabList) findComponentById(ResourceTable.Id_tab_list);
    newsList = (ListContainer) findComponentById(ResourceTable.Id_news_list);
    loadingContainer = (DirectionalLayout) findComponentById(ResourceTable.Id_loading_container);
    
    // 初始化适配器
    adapter = new NewsListAdapter(this);
    newsList.setProvider(adapter);
    
    // 设置列表滚动监听,用于分页加载
    newsList.setReboundEffect(true);
    newsList.setReboundListener(new ReboundEffect.ReboundListener() {
        @Override
        public void onReboundComplete(Component component, int position) {
            if (position >= adapter.getCount() - 3) {
                loadMoreData();
            }
        }
    });
    
    // 设置分类标签点击事件
    tabList.addTabSelectedListener(new TabList.TabSelectedListener() {
        @Override
        public void onSelected(TabList.Tab tab) {
            int index = tabList.getSelectedTabIndex();
            if (currentCategoryIndex != index) {
                currentCategoryIndex = index;
                currentPage = 1;
                loadNews(true);
            }
        }
        
        @Override
        public void onUnselected(TabList.Tab tab) {}
        
        @Override
        public void onReselected(TabList.Tab tab) {}
    });
}

private void loadCategories() {
    // 从本地或网络获取分类数据
    categories.add(new NewsCategory("1", "推荐", true));
    categories.add(new NewsCategory("2", "时政", false));
    categories.add(new NewsCategory("3", "科技", false));
    categories.add(new NewsCategory("4", "财经", false));
    categories.add(new NewsCategory("5", "娱乐", false));
    categories.add(new NewsCategory("6", "体育", false));
    categories.add(new NewsCategory("7", "国际", false));
    
    // 添加到TabList
    for (NewsCategory category : categories) {
        TabList.Tab tab = tabList.new Tab(this);
        tab.setText(category.getName());
        tabList.addTab(tab);
    }
    tabList.selectTabAt(0);
}

private void loadNews(boolean reset) {
    if (reset) {
        // 显示骨架屏
        showSkeletonLoading();
        currentPage = 1;
    } else {
        // 显示底部加载状态
        loadingContainer.setVisibility(Component.VISIBLE);
    }
    
    adapter.setLoading(true);
    adapter.setHasMore(true);
    adapter.notifyDataChanged();
    
    // 模拟网络请求
    getUITaskDispatcher().delayDispatch(() -> {
        List<NewsItem> newsData = fetchNewsData(
            categories.get(currentCategoryIndex).getId(), 
            currentPage, 
            PAGE_SIZE
        );
        
        if (newsData != null) {
            if (reset) {
                // 清除骨架屏
                hideSkeletonLoading();
            }
            
            adapter.updateData(newsData, reset);
            adapter.setLoading(false);
            adapter.setHasMore(newsData.size() >= PAGE_SIZE);
            
            if (!reset) {
                loadingContainer.setVisibility(Component.INVISIBLE);
            }
            
            adapter.notifyDataChanged();
            currentPage++;
        } else {
            // 处理错误
            showErrorToast();
        }
    }, 1500); // 模拟网络延迟
}

private List<NewsItem> fetchNewsData(String categoryId, int page, int size) {
    // 模拟数据获取
    List<NewsItem> items = new ArrayList<>();
    for (int i = 0; i < size; i++) {
        String id = categoryId + "_" + page + "_" + i;
        String title = "这是" + categories.get(currentCategoryIndex).getName() + 
            "分类的第" + (page * size + i) + "条新闻标题";
        
        items.add(new NewsItem(
            id,
            title,
            "https://example.com/news/" + id + ".jpg",
            "新闻来源" + (i % 5),
            System.currentTimeMillis() - (i * 600000) + "",
            (int) (Math.random() * 500)
        ));
    }
    return items;
}

private void loadMoreData() {
    if (!adapter.isLoading() && adapter.hasMore()) {
        loadNews(false);
    }
}

private void showSkeletonLoading() {
    // 临时显示骨架屏
    ListContainer tempList = new ListContainer(this);
    tempList.setId(ResourceTable.Id_skeleton_list);
    tempList.setProvider(new SkeletonAdapter(this));
    tempList.setLayoutConfig(new ComponentContainer.LayoutConfig(
        ComponentContainer.LayoutConfig.MATCH_PARENT, 
        ComponentContainer.LayoutConfig.MATCH_PARENT
    ));
    
    // 替换列表
    ComponentContainer parent = (ComponentContainer) newsList.getParent();
    parent.replaceComponent(newsList, tempList);
}

private void hideSkeletonLoading() {
    ComponentContainer parent = (ComponentContainer) 
        findComponentById(ResourceTable.Id_skeleton_list).getParent();
    parent.replaceComponent(
        findComponentById(ResourceTable.Id_skeleton_list), 
        newsList
    );
}

private void showErrorToast() {
    getUITaskDispatcher().asyncDispatch(() -> {
        new ToastDialog(this)
            .setText("加载失败,请重试")
            .setAlignment(Alignment.BOTTOM)
            .show();
    });
}

// 骨架屏适配器
private static class SkeletonAdapter extends BaseItemProvider {
    private Context context;
    
    public SkeletonAdapter(Context context) {
        this.context = context;
    }
    
    @Override
    public int getCount() {
        return 6; // 显示6个骨架屏
    }
    
    @Override
    public Component getComponent(int position, Component convertView, ComponentContainer parent) {
        if (convertView == null) {
            convertView = LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_item_skeleton, null, false);
        }
        return convertView;
    }
}

}
四、关键功能实现

  1. 分类标签栏动态添加
    private void updateCategories(List<NewsCategory> newCategories) {
    // 移除原有标签
    tabList.removeAllTabs();

    // 添加新标签
    for (NewsCategory category : newCategories) {
    TabList.Tab tab = tabList.new Tab(this);
    tab.setText(category.getName());

     // 设置自定义样式
     Text tabText = (Text) tab.getComponent();
     tabText.setTextSize(16);
     tabText.setPadding(8, 8, 8, 8);
     
     tabList.addTab(tab);
    

    }

    // 恢复选中状态
    if (newCategories.size() > 0) {
    tabList.selectTabAt(0);
    currentCategoryIndex = 0;
    }
    }

  2. 新闻卡片点击动效
    private void setupCardAnimation(Component card) {
    // 设置默认缩放
    card.setScaleX(1.0f);
    card.setScaleY(1.0f);

    // 点击动效
    card.setTouchEventListener((component, event) -> {
    if (event.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
    AnimatorProperty scaleDown = new AnimatorProperty();
    scaleDown.setDuration(100).scaleX(0.98f).scaleY(0.98f);
    scaleDown.apply(component);
    } else if (event.getAction() == TouchEvent.PRIMARY_POINT_UP) {
    AnimatorProperty scaleUp = new AnimatorProperty();
    scaleUp.setDuration(100).scaleX(1.0f).scaleY(1.0f);
    scaleUp.apply(component);
    }
    return false;
    });
    }

  3. 图片加载实现
    public class ImageLoader {
    public static void loadImage(Context context, Image imageView, String url, int placeholder) {
    if (TextUtils.isEmpty(url)) {
    imageView.setPixelMap(placeholder);
    return;
    }

     // 设置占位图
     imageView.setPixelMap(placeholder);
     
     // 异步加载图片
     AsyncTask<Void, Void, PixelMap> task = new AsyncTask<Void, Void, PixelMap>() {
         @Override
         protected PixelMap doInBackground(Void... voids) {
             try {
                 // 模拟网络加载
                 Thread.sleep(300);
                 
                 // 实际应用中可以使用鸿蒙的ImageSource
                 ImageSource imageSource = new ImageSource(
                     new URL(url).openConnection().getInputStream()
                 );
                 return imageSource.createPixelmap(null);
             } catch (Exception e) {
                 return null;
             }
         }
         
         @Override
         protected void onPostExecute(PixelMap pixelMap) {
             if (pixelMap != null) {
                 imageView.setPixelMap(pixelMap);
             } else {
                 // 加载失败处理
             }
         }
     };
     
     task.execute();
    

    }
    }

  4. 网络请求封装
    public class ApiClient {
    public static void getNewsList(String category, int page, int size,
    ApiCallback<List<NewsItem>> callback) {
    // 创建HTTP请求
    HttpRequest request = new HttpRequest();
    request.setMethod(HttpMethod.GET);
    request.setUri(“https://api.example.com/news?category=” + category +
    “&page=” + page + “&size=” + size);

     // 创建HttpTask
     HttpTask task = new HttpTask(request, new HttpCallback() {
         @Override
         public void onResponse(HttpResponse response) {
             if (response.getResult() != null && response.getResult().getCode() == 200) {
                 try {
                     // 解析JSON数据
                     List<NewsItem> newsList = parseNewsJson(response.getResult().getString());
                     callback.onSuccess(newsList);
                 } catch (Exception e) {
                     callback.onError(e.getMessage());
                 }
             } else {
                 callback.onError("Server error: " + response.getResult().getCode());
             }
         }
         
         @Override
         public void onError(HttpError error) {
             callback.onError(error.getMessage());
         }
     });
     
     task.execute();
    

    }

    private static List<NewsItem> parseNewsJson(String json) {
    // 使用GSON或手动解析JSON
    List<NewsItem> items = new ArrayList<>();
    // 解析逻辑…
    return items;
    }
    }

public interface ApiCallback<T> {
void onSuccess(T result);
void onError(String message);
}
五、性能优化技巧

  1. 图片加载优化
    // 使用内存缓存
    private static final LruCache<String, PixelMap> memoryCache =
    new LruCache<>(20 * 1024 * 1024); // 20MB

public static void loadImageWithCache(Context context, Image imageView,
String url, int placeholder) {
// 检查内存缓存
PixelMap cached = memoryCache.get(url);
if (cached != null) {
imageView.setPixelMap(cached);
return;
}

// 异步加载
AsyncTask<Void, Void, PixelMap> task = new AsyncTask<Void, Void, PixelMap>() {
    @Override
    protected PixelMap doInBackground(Void... voids) {
        try {
            // 检查磁盘缓存...
            
            // 网络加载
            ImageSource imageSource = new ImageSource(
                new URL(url).openConnection().getInputStream()
            );
            PixelMap pixelMap = imageSource.createPixelmap(null);
            
            // 放入缓存
            memoryCache.put(url, pixelMap);
            return pixelMap;
        } catch (Exception e) {
            return null;
        }
    }
    
    @Override
    protected void onPostExecute(PixelMap pixelMap) {
        if (pixelMap != null) {
            imageView.setPixelMap(pixelMap);
        }
    }
};

task.execute();

}
2. 列表滚动优化
// 在适配器中实现
@Override
public Component getComponent(int position, Component convertView, ComponentContainer parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutScatter.getInstance(context)
.parse(ResourceTable.Layout_item_news_card, null, false);
holder.image = (Image) convertView.findComponentById(ResourceTable.Id_news_image);
holder.title = (Text) convertView.findComponentById(ResourceTable.Id_news_title);
// 其他组件…

    convertView.setTag(holder);
} else {
    holder = (ViewHolder) convertView.getTag();
}

// 绑定数据...

// 图片加载优化:暂停滚动时的加载
if (isScrolling) {
    holder.image.setPixelMap(ResourceTable.Media_placeholder);
} else {
    loadImageWithCache(holder.image, newsList.get(position).getImageUrl());
}

return convertView;

}

// 设置滚动监听
newsList.setScrollListener(new PageScroller.ScrollListener() {
@Override
public void onScrollStarted() {
adapter.setScrolling(true);
}

@Override
public void onScrollFinished() {
    adapter.setScrolling(false);
    adapter.notifyDataChanged();
}

@Override
public void onPageSelected(int position) {}

});
3. 数据分页预加载
private void loadMoreData() {
// 提前预加载下一页
if (currentPage > 1 && !adapter.isLoading() && adapter.hasMore() &&
(totalItemCount - visibleItemCount) <= preloadThreshold) {

    loadNews(false);
}

}
六、用户体验优化

  1. 骨架屏动效
    public class SkeletonShimmer {
    public static void applyTo(Component component) {
    ShapeElement background = new ShapeElement();
    background.setRgbColor(RgbColor.fromArgbInt(0xFFF0F0F0));
    background.setCornerRadius(component.getHeight() / 4);
    component.setBackground(background);

     // 创建闪烁动效
     AnimatorProperty animator = new AnimatorProperty();
     animator.setDuration(1500)
             .setLoopedCount(Animator.INFINITE);
     
     // 创建关键帧动画
     Keyframe start = Keyframe.ofFloat(0f, 0.8f);
     Keyframe middle = Keyframe.ofFloat(0.5f, 1.0f);
     Keyframe end = Keyframe.ofFloat(1f, 0.8f);
     
     PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe(
         "alpha", start, middle, end);
     animator.setValues(holder);
     
     component.setComponentAnimator(animator);
    

    }
    }

  2. 网络状态感知
    private void initNetworkListener() {
    NetManager netManager = NetManager.getInstance(this);
    netManager.addNetStatusCallback(new NetStatusCallback() {
    @Override
    public void onAvailable(Network network) {
    // 网络恢复,自动重试
    if (lastLoadFailed) {
    loadNews(true);
    }
    }

     @Override
     public void onLost(Network network) {
         // 网络断开
         showNetworkError();
     }
    

    });
    }

private void showNetworkError() {
new ToastDialog(this)
.setText(“网络连接已断开”)
.setAlignment(Alignment.BOTTOM)
.show();
}
3. 主题适配
<!-- 在color.json中定义深色模式颜色 -->
{
“color”: [
{
“name”: “card_background_dark”,
“value”: “#1E1E1E”
},
{
“name”: “text_primary_dark”,
“value”: “#E0E0E0”
}
]
}

<!-- 在布局中使用主题变量 -->
<DirectionalLayout
ohos:background_element=“$color:card_background_dark”>

<Text
    ohos:text_color="$color:text_primary_dark"/>

</DirectionalLayout>
总结
本文实现的鸿蒙5新闻列表页具有以下特点:

​​现代化的UI设计​​
简洁美观的顶部导航栏
交互友好的分类标签栏
符合Material Design的卡片布局
​​高性能实现​​
优化的列表滚动性能
图片异步加载与缓存
高效的分页数据加载
​​出色的用户体验​​
平滑的骨架屏加载动画
网络状态感知与自动重试
深色模式适配支持
​​可维护的代码结构​​
清晰的架构分层
模块化功能组件
完善的错误处理机制
实际开发中可以在此基础上扩展功能:

实现下拉刷新功能
添加收藏/分享功能
集成新闻搜索功能
增加离线缓存支持
通过遵循鸿蒙5的开发规范和最佳实践,开发者可以创建出体验优秀、性能高效的新闻类应用。

分类
标签
收藏
回复
举报
回复
    相关推荐