iOS-電子書開發 筆記

来源:https://www.cnblogs.com/wangkejia/archive/2018/02/09/8435435.html
-Advertisement-
Play Games

前言 剛接手電筒子書項目時,和安卓開發者pt Cai老師【aipiti Cai,一個我很敬佩很資深的開發工程師,設計領域:c++、Java、安卓、QT等】共同商議了一下,因為項目要做要同步,移動端【手機端】和PC【電腦端】的同步問題,讓我們無法決定該用那種方式去呈現電子書,因為PC要展示的電子書有網路 ...


前言

剛接手電筒子書項目時,和安卓開發者pt Cai老師【aipiti Cai,一個我很敬佩很資深的開發工程師,設計領域:c++、Java、安卓、QT等】共同商議了一下,因為項目要做要同步,移動端【手機端】和PC【電腦端】的同步問題,讓我們無法決定該用那種方式去呈現電子書,因為PC要展示的電子書有網路圖片,有HTML標簽,主要功能是能做標記(塗色、劃線、書簽等),而且後臺數據源返回的只有這一種格式:HTML;所以我們第一時間想到了可以用載入網頁的Webview來做;pt Cai老師做了一些基於JS的分頁及手勢操作,然後對圖片進行了適配,但是當我在測試Webview時,效果並不盡人意:

 

  • Webview渲染比較慢,載入需要一定的等待時間,體驗不是很好;
  • Webview記憶體泄漏比較嚴重;
  • Webview的與本地的交互,交互是有一定的延時,而且對於不斷地傳遞參數不好控制操作;

 

引入Coretext

通過上面的測試,我決定放棄了Webview,用Coretext來嘗試做這些排版和操作;我在網上查了很多資料,從對Coretext的基本開始瞭解,然後查看了猿題庫開發者的博客,在其中學到了不少東西,然後就開始試著慢慢的用Coretext來嘗試;

demo

1.主框架

做電子書閱讀,首先要有一個翻滾閱讀頁的一個框架,我並沒有選擇用蘋果自帶的 UIPageViewController 因為控制效果不是很好,我再Git上找了一個不錯的 DZMCoverAnimation,因為是做demo測試,就先選擇一個翻滾閱讀頁做效果,這個覆蓋翻頁的效果如下:

 

2.解析數據源

首先看一下數據源demo,我要求json數據最外層必須是P標簽,P標簽不能嵌套P標簽,但可以包含Img和Br標簽,Img標簽內必須含有寬高屬性,以便做排版時適配,最終的數據源為

 

然後我在項目中用CocoaPods引入解析HTML文件的 hpple 三方庫,在解析工具類CoreTextSource中添加解析數據模型和方法,假如上面的這個數據源是一章的內容,我把這一章內容最外層的每個P標簽當做一個段落,遍歷每個段落,然後在遍歷每個段落裡面的內容和其他標簽;

 

CoreTextSource.h

#import <Foundation/Foundation.h>
#import <hpple/TFHpple.h>

#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger,CoreTextSourceType){
    ///文本
    CoreTextSourceTypeTxt = 1,
    ///圖片
    CoreTextSourceTypeImage
};

/**
 文本
 */
@interface CoreTextTxtSource : NSObject
@property (nonatomic,strong) NSString *content;
@end

/**
 圖片
 */
@interface CoreTextImgSource : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,assign) CGFloat width;
@property (nonatomic,assign) CGFloat height;
@property (nonatomic,strong) NSString *url;
// 此坐標是 CoreText 的坐標系,而不是UIKit的坐標系
@property (nonatomic,assign) NSInteger position;
@property (nonatomic,assign) CGRect imagePosition;
@end

/**
 段落內容
 */
@interface CoreTextParagraphSource : NSObject
@property (nonatomic,assign) CoreTextSourceType type;
@property (nonatomic,strong) CoreTextImgSource *imgData;
@property (nonatomic,strong) CoreTextTxtSource *txtData;
@end
///電子書數據源
@interface CoreTextSource : NSObject
///解析HTML格式
+ (NSArray *)arrayReaolveChapterHtmlDataWithFilePath:(NSString *)filePath;
@end
View Code

CoreTextSource.m

#import "CoreTextSource.h"

@implementation CoreTextImgSource

@end
@implementation CoreTextParagraphSource

@end
@implementation CoreTextTxtSource

@end

@implementation CoreTextSource

+ (NSArray *)arrayReaolveChapterHtmlDataWithFilePath:(NSString *)filePath{
    NSData  * data   = [NSData dataWithContentsOfFile:filePath];
    
    TFHpple * dataSource = [[TFHpple alloc] initWithHTMLData:data];
    NSArray * elements = [dataSource searchWithXPathQuery:@"//p"];
    
    NSMutableArray *arrayData = [NSMutableArray array];
    
    for (TFHppleElement *element in elements) {
        NSArray *arrrayChild = [element children];
        for (TFHppleElement *elementChild in arrrayChild) {
            CoreTextParagraphSource *paragraphSource = [[CoreTextParagraphSource alloc]init];
            NSString *type = [elementChild tagName];
            if ([type isEqualToString:@"text"]) {
                CoreTextTxtSource *text = [[CoreTextTxtSource alloc]init];
                text.content = elementChild.content;
                paragraphSource.txtData = text;
                paragraphSource.type = CoreTextSourceTypeTxt;
            }
            else if ([type isEqualToString:@"img"]){
                CoreTextImgSource *image = [[CoreTextImgSource alloc]init];
                NSDictionary *dicAttributes = [elementChild attributes];
                image.name = [dicAttributes[@"src"] lastPathComponent];
                image.url = dicAttributes[@"src"];
                image.width = [dicAttributes[@"width"] floatValue];
                image.height = [dicAttributes[@"height"] floatValue];
                paragraphSource.imgData = image;
                paragraphSource.type = CoreTextSourceTypeImage;
                
                if (image.width >= (Scr_Width - 30)) {
                    CGFloat ratioHW = image.height/image.width;
                    image.width = Scr_Width - 30;
                    image.height = image.width * ratioHW;
                }
            }
            else if ([type isEqualToString:@"br"]){
                CoreTextTxtSource *text = [[CoreTextTxtSource alloc]init];
                text.content = @"\n";
                paragraphSource.txtData = text;
                paragraphSource.type = CoreTextSourceTypeTxt;
            }
            
            [arrayData addObject:paragraphSource];
        }
        
        ///每個個<P>後加換行
        CoreTextParagraphSource *paragraphNewline = [[CoreTextParagraphSource alloc]init];
        CoreTextTxtSource *textNewline = [[CoreTextTxtSource alloc]init];
        textNewline.content = @"\n";
        paragraphNewline.txtData = textNewline;
        paragraphNewline.type = CoreTextSourceTypeTxt;
        [arrayData addObject:paragraphNewline];
    }
    
    return arrayData;
}
@end
View Code

3.圖片處理和分頁

添加好CoreTextSource類之後,就可以通過 arrayReaolveChapterHtmlDataWithFilePath 方法獲取這一章的所有段落內容;但是還有一個問題,既然用Coretext來渲染,那圖片要在渲染之前下載好,從本地獲取下載好的圖片進行渲染,具體什麼時候下載,視項目而定;我在CoreTextDataTools類中添加了圖片下載方法,該類主要用於分頁;在分頁之前,添加每個閱讀頁的model -> CoreTextDataModel,具體圖片的渲染,先詳看CoreTextDataTools分頁類中 wkj_coreTextPaging 方法和其中引用到的方法;

CoreTextDataModel.h

#import <Foundation/Foundation.h>

///標記顯示模型
@interface CoreTextMarkModel : NSObject
@property (nonatomic,assign) BookMarkType type;
@property (nonatomic,assign) NSRange range;
@property (nonatomic,strong) NSString *content;
@property (nonatomic,strong) UIColor *color;
@end

@interface CoreTextDataModel : NSObject
///
@property (nonatomic,assign) CTFrameRef ctFrame;
@property (nonatomic,strong) NSAttributedString *content;
@property (nonatomic,assign) NSRange range;
///圖片數據模型數組 CoreTextImgSource
@property (nonatomic,strong) NSArray *arrayImage;
///標記數組
@property (nonatomic,copy) NSArray *arrayMark;
@end
View Code 

CoreTextDataModel.m

#import "CoreTextDataModel.h"
@implementation CoreTextMarkModel

@end

@implementation CoreTextDataModel
- (void)setCtFrame:(CTFrameRef)ctFrame{
    if (_ctFrame != ctFrame) {
        if (_ctFrame != nil) {
            CFRelease(_ctFrame);
        }
        CFRetain(ctFrame);
        _ctFrame = ctFrame;
    }
}
@end
View Code

 

CoreTextDataTools.h

///圖片下載
+ (void)wkj_downloadBookImage:(NSArray *)arrayParagraph;
///分頁
+ (NSArray *)wkj_coreTextPaging:(NSAttributedString *)str
                       textArea:(CGRect)textFrame
           arrayParagraphSource:(NSArray *)arrayParagraph;
///根據一個章節的所有段落內容,來生成 AttributedString 包括圖片
+ (NSAttributedString *)wkj_loadChapterParagraphArray:(NSArray *)arrayArray;
View Code

CoreTextDataTools.m

#import "CoreTextDataTools.h"
#import <SDWebImage/UIImage+MultiFormat.h>

@implementation CoreTextDataTools
+ (void)wkj_downloadBookImage:(NSArray *)arrayParagraph{
    dispatch_group_t group = dispatch_group_create();
    // 有多張圖片URL的數組
    for (CoreTextParagraphSource *paragraph in arrayParagraph) {
        if (paragraph.type == CoreTextSourceTypeTxt) {
            continue;
        }
        
        dispatch_group_enter(group);
        // 需要載入圖片的控制項(UIImageView, UIButton等)
        NSData *data = [NSData dataWithContentsOfURL:[NSURL  URLWithString:paragraph.imgData.url]];
        UIImage *image = [UIImage sd_imageWithData:data];
        // 本地沙盒目錄
        NSString *path = wkj_documentPath;
        ///創建文件夾
        NSString *folderName = [path stringByAppendingPathComponent:@"wkjimage"];
        
        if (![[NSFileManager defaultManager]fileExistsAtPath:folderName]) {
            
            [[NSFileManager defaultManager] createDirectoryAtPath:folderName  withIntermediateDirectories:YES  attributes:nil error:nil];
            
        }else{
            NSLog(@"有這個文件了");
        }
        
        // 得到本地沙盒中名為"MyImage"的路徑,"MyImage"是保存的圖片名
        //        NSString *imageFilePath = [path stringByAppendingPathComponent:@"MyImage"];
        
        // 將取得的圖片寫入本地的沙盒中,其中0.5表示壓縮比例,1表示不壓縮,數值越小壓縮比例越大
        
        folderName = [folderName stringByAppendingPathComponent:[paragraph.imgData.url lastPathComponent]];
        
        BOOL success = [UIImageJPEGRepresentation(image, 0.1) writeToFile:folderName  atomically:YES];
        if (success){
            NSLog(@"寫入本地成功");
        }
        
        dispatch_group_leave(group);
        
    }
    // 下載圖片完成後, 回到主線
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 刷新UI
        
    });
}
/**
 CoreText 分頁
 str: NSAttributedString屬性字元串
 textFrame: 繪製區域
 */
+ (NSArray *)wkj_coreTextPaging:(NSAttributedString *)str
                       textArea:(CGRect)textFrame
           arrayParagraphSource:(NSArray *)arrayParagraph{
    NSMutableArray *arrayCoretext = [NSMutableArray array];
    
    CFAttributedStringRef cfStrRef = (__bridge CFAttributedStringRef)str;
    CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(cfStrRef);
    CGPathRef path = CGPathCreateWithRect(textFrame, NULL);
    
    int textPos = 0;
    NSUInteger strLength = [str length];
    while (textPos < strLength)  {
        //設置路徑
        CTFrameRef frame = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(textPos, 0), path, NULL);
        CFRange frameRange = CTFrameGetVisibleStringRange(frame);
        NSRange range = NSMakeRange(frameRange.location, frameRange.length);
        
        //        [arrayPagingRange addObject:[NSValue valueWithRange:range]];
        //        [arrayPagingStr addObject:[str attributedSubstringFromRange:range]];
    
        
        CoreTextDataModel *model = [[CoreTextDataModel alloc]init];
        model.ctFrame = frame;
        model.range = range;
        model.content = [str attributedSubstringFromRange:range];
        model.arrayImage = [self wkj_arrayCoreTextImgRect:[self wkj_arrayCoreTextImg:arrayParagraph range:range] cfFrame:frame];
        
        [arrayCoretext addObject:model];
        //移動
        textPos += frameRange.length;
        CFRelease(frame);
    }
    CGPathRelease(path);
    CFRelease(framesetterRef);
    //    return arrayPagingStr;
    return arrayCoretext;
}
///獲取每頁區域記憶體在的圖片
+ (NSArray *)wkj_arrayCoreTextImg:(NSArray *)arrayParagraph
                                  range:(NSRange)range{
    NSMutableArray *array = [NSMutableArray array];
    
    for (CoreTextParagraphSource *paragraph in arrayParagraph) {
        if (paragraph.type == CoreTextSourceTypeTxt) {
            continue;
        }
        
        if (paragraph.imgData.position >= range.location &&
            paragraph.imgData.position < (range.location + range.length)) {
            [array addObject:paragraph.imgData];
        }
    }
    
    return array;
}
///獲取每個區域記憶體在的圖片位置
+ (NSArray *)wkj_arrayCoreTextImgRect:(NSArray *)arrayCoreTextImg cfFrame:(CTFrameRef)frameRef{
    NSMutableArray *arrayImgData = [NSMutableArray array];
    
    if (arrayCoreTextImg.count == 0) {
        return arrayCoreTextImg;
    }
    NSArray *lines = (NSArray *)CTFrameGetLines(frameRef);
    NSUInteger lineCount = [lines count];
    CGPoint lineOrigins[lineCount];
    CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), lineOrigins);
    int imgIndex = 0;
    CoreTextImgSource * imageData = arrayCoreTextImg[0];
    for (int i = 0; i < lineCount; ++i) {

        CTLineRef line = (__bridge CTLineRef)lines[i];
        NSArray * runObjArray = (NSArray *)CTLineGetGlyphRuns(line);
        for (id runObj in runObjArray) {
            CTRunRef run = (__bridge CTRunRef)runObj;
            NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);
            CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
            if (delegate == nil) {///如果代理為空,則未找到設置的空白字元代理
                continue;
            }
            

            
            CoreTextImgSource * metaImgSource = CTRunDelegateGetRefCon(delegate);
            if (![metaImgSource isKindOfClass:[CoreTextImgSource class]]) {
                continue;
            }
            
            CGRect runBounds;
            CGFloat ascent;
            CGFloat descent;
            runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
            runBounds.size.height = ascent + descent;
            
            CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
            runBounds.origin.x = lineOrigins[i].x + xOffset;
            runBounds.origin.y = lineOrigins[i].y;
            runBounds.origin.y -= descent;
            
            CGPathRef pathRef = CTFrameGetPath(frameRef);
            CGRect colRect = CGPathGetBoundingBox(pathRef);
            
            CGRect delegateBounds = CGRectOffset(runBounds, colRect.origin.x, colRect.origin.y);
            
            imageData.imagePosition = delegateBounds;
            CoreTextImgSource *img = imageData;
            [arrayImgData addObject:img];
            imgIndex++;
            if (imgIndex == arrayCoreTextImg.count) {
                imageData = nil;
                break;
            } else {
                imageData = arrayCoreTextImg[imgIndex];
            }
        }
        
        if (imgIndex == arrayCoreTextImg.count) {
            break;
        }
        
    }
    
    return arrayImgData;
    
}




///獲取屬性字元串字典
+ (NSMutableDictionary *)wkj_attributes{
    CGFloat fontSize = [BookThemeManager sharedManager].fontSize;
    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
    ///行間距
    CGFloat lineSpacing = [BookThemeManager sharedManager].lineSpace;
    ///首行縮進
    CGFloat firstLineHeadIndent = [BookThemeManager sharedManager].firstLineHeadIndent;
    ///段落間距
    CGFloat paragraphSpacing = [BookThemeManager sharedManager].ParagraphSpacing;
    //換行模式
    CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
    const CFIndex kNumberOfSettings = 6;
    CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
        ///行間距
        { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing },
        { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing },
        { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing },
        ///首行縮進
        { kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineHeadIndent },
        ///換行模式
        { kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreak },
        ///段落間距
        { kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpacing }
    };
    
    CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
    
    UIColor * textColor = [BookThemeManager sharedManager].textColor;
    
    NSMutableDictionary * dict = [NSMutableDictionary dictionary];
    dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor;
    dict[(id)kCTFontAttributeName] = (__bridge id)fontRef;
    dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef;
    CFRelease(theParagraphRef);
    CFRelease(fontRef);
    return dict;
}






///根據一個章節的所有段落內容,來生成 AttributedString 包括圖片
+ (NSAttributedString *)wkj_loadChapterParagraphArray:(NSArray *)arrayArray{
    
    NSMutableAttributedString *resultAtt = [[NSMutableAttributedString alloc] init];
    
    for (CoreTextParagraphSource *paragraph in arrayArray) {
        if (paragraph.type == CoreTextSourceTypeTxt) {///文本
            NSAttributedString *txtAtt = [self wkj_parseContentFromCoreTextParagraph:paragraph];
            [resultAtt appendAttributedString:txtAtt];
        }
        else if (paragraph.type == CoreTextSourceTypeImage){///圖片
            paragraph.imgData.position = resultAtt.length;
            NSAttributedString *imageAtt = [self wkj_parseImageFromCoreTextParagraph:paragraph];
            [resultAtt appendAttributedString:imageAtt];
        }
    }
    
    return resultAtt;
}

///根據段落文本內容獲取 AttributedString
+ (NSAttributedString  *)wkj_parseContentFromCoreTextParagraph:(CoreTextParagraphSource *)paragraph{
    NSMutableDictionary *attributes = [self wkj_attributes];
    return [[NSAttributedString alloc] initWithString:paragraph.txtData.content attributes:attributes];
}


/////根據段落圖片內容獲取 AttributedString 空白占位符
+ (NSAttributedString *)wkj_parseImageFromCoreTextParagraph:(CoreTextParagraphSource *)paragraph{

    CTRunDelegateCallbacks callbacks;
    memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
    callbacks.version = kCTRunDelegateVersion1;
    callbacks.getAscent = ascentCallback;
    callbacks.getDescent = descentCallback;
    callbacks.getWidth = widthCallback;
    CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)(paragraph.imgData));

    // 使用0xFFFC作為空白的占位符
    unichar objectReplacementChar = 0xFFFC;
    NSString * content = [NSString stringWithCharacters:&objectReplacementChar length:1];
    NSMutableDictionary * attributes = [self wkj_attributes];
    //    attributes[(id)kCTBackgroundColorAttributeName] = (id)[UIColor yellowColor].CGColor;
    NSMutableAttributedString * space = [[NSMutableAttributedString alloc] initWithString:content attributes:attributes];
    CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1),
                                   kCTRunDelegateAttributeName, delegate);
    CFRelease(delegate);
    return space;
}

//+ (NSAttributedString *)wkj_NewlineAttributes{
//    CTRunDelegateCallbacks callbacks;
//    memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
//    callbacks.version = kCTRunDelegateVersion1;
//    callbacks.getAscent = ascentCallback;
//    callbacks.getDescent = descentCallback;
//    callbacks.getWidth = widthCallback;
//    CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)(paragraph));
//
//    // 使用0xFFFC作為空白的占位符
//    unichar objectReplacementChar = 0xFFFC;
//    NSString * content = [NSString stringWithCharacters:&objectReplacementChar length:1];
//    NSMutableDictionary * attributes = [self wkj_attributes];
//    //    attributes[(id)kCTBackgroundColorAttributeName] = (id)[UIColor yellowColor].CGColor;
//    NSMutableAttributedString * space = [[NSMutableAttributedString alloc] initWithString:content attributes:attributes];
//    CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1),
//                                   kCTRunDelegateAttributeName, delegate);
//    CFRelease(delegate);
//    return space;
//}

static CGFloat ascentCallback(void *ref){
//    return [(NSNumber*)[(__bridge NSDictionary*)ref objectForKey:@"height"] floatValue];
    CoreTextImgSource *refP = (__bridge CoreTextImgSource *)ref;
    return refP.height;
}

static CGFloat descentCallback(void *ref){
    return 0;
}

static CGFloat widthCallback(void* ref){
//    return [(NSNumber*)[(__bridge NSDictionary*)ref objectForKey:@"width"] floatValue];
    
    CoreTextImgSource *refP = (__bridge CoreTextImgSource *)ref;
    return refP.width;
}

@end
View Code

添加好CoreTextDataTools類之後,就可以通過 wkj_downloadBookImage 方法來下載圖片;圖片下載完之後,就可以對每頁顯示的內容區域進行分頁;劃線和塗色的一些方法在上一篇中已提到;

    ///獲取測試數據源文件
    NSString *path = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    ///獲取該章所有段落內容
    NSArray *arrayParagraphSource = [CoreTextSource arrayReaolveChapterHtmlDataWithFilePath:path];
    ///下載該章中的所有圖片
    [CoreTextDataTools wkj_downloadBookImage:arrayParagraphSource];
    ///根據一個章節的所有段落內容,來生成 AttributedString 包括圖片
    NSAttributedString *att = [CoreTextDataTools wkj_loadChapterParagraphArray:arrayParagraphSource];
    ///給章所有內容分頁 返回 CoreTextDataModel 數組
    NSArray *array = [CoreTextDataTools wkj_coreTextPaging:att textArea:CGRectMake(5, 5, self.view.bounds.size.width - 10, self.view.bounds.size.heigh                     t- 120) arrayParagraphSource:arrayParagraphSource];

 

4.效果

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • http://dcx.sybase.com/1101/en/dbprogramming_en11/ianywhere-data-sqlanywhere-saconnection-getschem6330755502-0.html http://razorsql.com/articles/sybase ...
  • 一、概述 mongodb是最接近關係型資料庫的NOSQL資料庫,它的存儲方式非常的靈活;以至於你會將它看成是一個經過冗餘過的關係型資料庫的表,這也是Mongodb原子性的一個特征。由於沒有關係型資料庫的表之間的關聯關係和事務性所以Mongodb插入和更新的效率非常的高,同時也支持索引。我們在查詢的時 ...
  • ViewPager中切換界面Fragment被銷毀的問題分析 使用ViewPager+Fragment實現界面切換,當界面數量大於3時,出現二次滑動後數據消失的情況,下麵由Fragment生命周期進行分析 簡單解析: 使用pager=3進行測試,當界面由2切換到1的時候,3界面對應的Fragment ...
  • 今天同事誤上傳一個庫,然後又刪除了。。。 我剛好把他上傳的庫給down下來了。。。然後項目一直報錯,clean。。。重新編譯。。。刪build文件。。。。全都不管用 好幾個人研究了好久,只能猜測是緩存問題。。。把項目的緩存全刪了沒用。。。。那應該是gradle緩存的問題。。。 可以我電腦上的grad ...
  • There may be a situation, when you need to execute a block of code several number of times. In general, statements are executed sequentially: The firs ...
  • 目錄: 一、Viewpager的簡單介紹 二、簡單的Viewpager使用 三、簡單顯示圖片的Viewpager實現 四、廣告圖的實現及Viewpager指示器(小圓點)的實現 五、APP引導頁的實現 一、ViewPager介紹 官方文檔解釋: Layout manager that allows ...
  • 一、核心動畫概念 -導入QuartzCore.framework框架 1⃣ 開發步驟1.初始化一個動畫對象(CAAnimation)並且設置一些動畫相關屬性 2.CALayer中很多屬性都可以通過CAAnimation實現動畫效果,包括:opacity、position、transform、boun ...
  • 翻轉的動畫 旋轉動畫 偏移動畫 翻頁動畫 縮放動畫 取反的動畫效果是根據當前的動畫取他的相反的動畫 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...